int SkPDFOffsetMap::objectCount() const { return SkToInt(fOffsets.size() + 1); // Include the special zeroth object in the count.
}
int SkPDFOffsetMap::emitCrossReferenceTable(SkWStream* s) const { int xRefFileOffset = SkToInt(difference(s->bytesWritten(), fBaseOffset));
s->writeText("xref\n0 ");
s->writeDecAsText(this->objectCount());
s->writeText("\n0000000000 65535 f \n"); for (int offset : fOffsets) {
SkASSERT(offset > 0); // Offset was set.
s->writeBigDecAsText(offset, 10);
s->writeText(" 00000 n \n");
} return xRefFileOffset;
} // ////////////////////////////////////////////////////////////////////////////////
#define SKPDF_MAGIC "\xD3\xEB\xE9\xE1" #ifndef SK_BUILD_FOR_WIN
static_assert((SKPDF_MAGIC[0] & 0x7F) == "Skia"[0], "");
static_assert((SKPDF_MAGIC[1] & 0x7F) == "Skia"[1], "");
static_assert((SKPDF_MAGIC[2] & 0x7F) == "Skia"[2], "");
static_assert((SKPDF_MAGIC[3] & 0x7F) == "Skia"[3], ""); #endif staticvoid serializeHeader(SkPDFOffsetMap* offsetMap, SkWStream* wStream) {
offsetMap->markStartOfDocument(wStream);
wStream->writeText("%PDF-1.4\n%" SKPDF_MAGIC "\n"); // The PDF spec recommends including a comment with four // bytes, all with their high bits set. "\xD3\xEB\xE9\xE1" is // "Skia" with the high bits set.
} #undef SKPDF_MAGIC
static SkPDFIndirectReference generate_page_tree(
SkPDFDocument* doc,
std::vector<std::unique_ptr<SkPDFDict>> pages, const std::vector<SkPDFIndirectReference>& pageRefs) { // PDF wants a tree describing all the pages in the document. We arbitrary // choose 8 (kMaxNodeSize) as the number of allowed children. The internal // nodes have type "Pages" with an array of children, a parent pointer, and // the number of leaves below the node as "Count." The leaves are passed // into the method, have type "Page" and need a parent pointer. This method // builds the tree bottom up, skipping internal nodes that would have only // one child.
SkASSERT(!pages.empty()); struct PageTreeNode {
std::unique_ptr<SkPDFDict> fNode;
SkPDFIndirectReference fReservedRef; int fPageObjectDescendantCount;
static std::vector<PageTreeNode> Layer(std::vector<PageTreeNode> vec, SkPDFDocument* doc) {
std::vector<PageTreeNode> result; static constexpr size_t kMaxNodeSize = 8; const size_t n = vec.size();
SkASSERT(n >= 1); const size_t result_len = (n - 1) / kMaxNodeSize + 1;
SkASSERT(result_len >= 1);
SkASSERT(n == 1 || result_len < n);
result.reserve(result_len);
size_t index = 0; for (size_t i = 0; i < result_len; ++i) { if (n != 1 && index + 1 == n) { // No need to create a new node.
result.push_back(std::move(vec[index++])); continue;
}
SkPDFIndirectReference parent = doc->reserveRef(); auto kids_list = SkPDFMakeArray(); int descendantCount = 0; for (size_t j = 0; j < kMaxNodeSize && index < n; ++j) {
PageTreeNode& node = vec[index++];
node.fNode->insertRef("Parent", parent);
kids_list->appendRef(doc->emit(*node.fNode, node.fReservedRef));
descendantCount += node.fPageObjectDescendantCount;
} auto next = SkPDFMakeDict("Pages");
next->insertInt("Count", descendantCount);
next->insertObject("Kids", std::move(kids_list));
result.push_back(PageTreeNode{std::move(next), parent, descendantCount});
} return result;
}
};
std::vector<PageTreeNode> currentLayer;
currentLayer.reserve(pages.size());
SkASSERT(pages.size() == pageRefs.size()); for (size_t i = 0; i < pages.size(); ++i) {
currentLayer.push_back(PageTreeNode{std::move(pages[i]), pageRefs[i], 1});
}
currentLayer = PageTreeNode::Layer(std::move(currentLayer), doc); while (currentLayer.size() > 1) {
currentLayer = PageTreeNode::Layer(std::move(currentLayer), doc);
}
SkASSERT(currentLayer.size() == 1); const PageTreeNode& root = currentLayer[0]; return doc->emit(*root.fNode, root.fReservedRef);
}
SkCanvas* SkPDFDocument::onBeginPage(SkScalar width, SkScalar height) {
SkASSERT(fCanvas.imageInfo().dimensions().isZero()); if (fPages.empty()) { // if this is the first page if the document.
{
SkAutoMutexExclusive autoMutexAcquire(fMutex);
serializeHeader(&fOffsetMap, this->getStream());
}
fInfoDict = this->emit(*SkPDFMetadata::MakeDocumentInformationDict(fMetadata)); if (fMetadata.fPDFA) {
fUUID = SkPDFMetadata::CreateUUID(fMetadata); // We use the same UUID for Document ID and Instance ID since this // is the first revision of this document (and Skia does not // support revising existing PDF documents). // If we are not in PDF/A mode, don't use a UUID since testing // works best with reproducible outputs.
fXMP = SkPDFMetadata::MakeXMPObject(fMetadata, fUUID, fUUID, this);
}
} // By scaling the page at the device level, we will create bitmap layer // devices at the rasterized scale, not the 72dpi scale. Bitmap layer // devices are created when saveLayer is called with an ImageFilter; see // SkPDFDevice::createDevice().
SkISize pageSize = (SkSize{width, height} * fRasterScale).toRound();
SkMatrix initialTransform; // Skia uses the top left as the origin but PDF natively has the origin at the // bottom left. This matrix corrects for that, as well as the raster scale.
initialTransform.setScaleTranslate(fInverseRasterScale, -fInverseRasterScale,
0, fInverseRasterScale * pageSize.height());
fPageDevice = sk_make_sp<SkPDFDevice>(pageSize, this, initialTransform);
reset_object(&fCanvas, fPageDevice);
fCanvas.scale(fRasterScale, fRasterScale);
fPageRefs.push_back(this->reserveRef()); return &fCanvas;
}
if (std::unique_ptr<SkPDFArray> annotations = getAnnotations()) {
page->insertObject("Annots", std::move(annotations));
fCurrentPageLinks.clear();
}
page->insertRef("Contents", SkPDFStreamOut(nullptr, std::move(pageContent), this)); // The StructParents unique identifier for each page is just its // 0-based page index.
page->insertInt("StructParents", SkToInt(this->currentPageIndex()));
// Tabs is PDF 1.5, but setting it checks an accessibility box.
page->insertName("Tabs", "S");
const SkMatrix& SkPDFDocument::currentPageTransform() const { static constexpr const SkMatrix gIdentity; // If not on a page (like when emitting a Type3 glyph) return identity. if (!this->hasCurrentPage()) { return gIdentity;
} return fPageDevice->initialTransform();
}
SkPDFStructTree::Mark SkPDFDocument::createMarkForElemId(int elemId) { // If the mark isn't on a page (like when emitting a Type3 glyph) // return a temporary mark not attached to the page or a structure element. if (!this->hasCurrentPage()) { return SkPDFStructTree::Mark();
} return fStructTree.createMarkForElemId(elemId, SkToUInt(this->currentPageIndex()));
}
int SkPDFDocument::createStructParentKeyForElemId(int elemId, SkPDFIndirectReference contentItem) { // Structure elements are tied to pages, so don't emit one if not on a page. if (!this->hasCurrentPage()) { return -1;
} return fStructTree.createStructParentKeyForElemId(elemId, contentItem,
SkToUInt(this->currentPageIndex()));
}
static std::vector<const SkPDFFont*> get_fonts(const SkPDFDocument& canon) {
std::vector<const SkPDFFont*> fonts;
fonts.reserve(canon.fStrikes.count());
canon.fStrikes.foreach([&fonts](const sk_sp<SkPDFStrike>& strike) { for (constauto& [unused, font] : strike->fFontMap) {
fonts.push_back(&font);
}
}); // Sort so the output PDF is reproducible.
std::sort(fonts.begin(), fonts.end(), [](const SkPDFFont* u, const SkPDFFont* v) { return u->indirectReference().fValue < v->indirectReference().fValue;
}); return fonts;
}
SkString SkPDFDocument::nextFontSubsetTag() { // PDF 32000-1:2008 Section 9.6.4 FontSubsets "The tag shall consist of six uppercase letters" // "followed by a plus sign" "different subsets in the same PDF file shall have different tags." // There are 26^6 or 308,915,776 possible values. So start in range then increment and mod.
uint32_t thisFontSubsetTag = fNextFontSubsetTag;
fNextFontSubsetTag = (fNextFontSubsetTag + 1u) % 308915776u;
void SkPDFDocument::onClose(SkWStream* stream) {
SkASSERT(fCanvas.imageInfo().dimensions().isZero()); if (fPages.empty()) {
this->waitForJobs(); return;
} auto docCatalog = SkPDFMakeDict("Catalog"); if (fMetadata.fPDFA) {
SkASSERT(fXMP != SkPDFIndirectReference());
docCatalog->insertRef("Metadata", fXMP); // Don't specify OutputIntents if we are not in PDF/A mode since // no one has ever asked for this feature.
docCatalog->insertObject("OutputIntents", make_srgb_output_intents(this));
}
if (!fNamedDestinations.empty()) {
docCatalog->insertRef("Dests", append_destinations(this, fNamedDestinations));
fNamedDestinations.clear();
}
// Handle tagged PDFs. if (SkPDFIndirectReference root = fStructTree.emitStructTreeRoot(this)) { // In the document catalog, indicate that this PDF is tagged. auto markInfo = SkPDFMakeDict("MarkInfo");
markInfo->insertBool("Marked", true);
docCatalog->insertObject("MarkInfo", std::move(markInfo));
docCatalog->insertRef("StructTreeRoot", root);
if (SkPDFIndirectReference outline = fStructTree.makeOutline(this)) {
docCatalog->insertRef("Outlines", outline);
}
}
// If ViewerPreferences DisplayDocTitle isn't set to true, accessibility checks will fail. if (!fMetadata.fTitle.isEmpty()) { auto viewerPrefs = SkPDFMakeDict("ViewerPreferences");
viewerPrefs->insertBool("DisplayDocTitle", true);
docCatalog->insertObject("ViewerPreferences", std::move(viewerPrefs));
}
SkString lang = fMetadata.fLang; if (lang.isEmpty()) {
lang = fStructTree.getRootLanguage();
} if (!lang.isEmpty()) {
docCatalog->insertTextString("Lang", lang);
}
auto docCatalogRef = this->emit(*docCatalog);
for (const SkPDFFont* f : get_fonts(*this)) {
f->emitSubset(this);
}
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.