1
2/*
3 * Copyright 2010 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "SkPDFCatalog.h"
11#include "SkPDFTypes.h"
12#include "SkStream.h"
13#include "SkTypes.h"
14
15SkPDFCatalog::SkPDFCatalog(SkPDFDocument::Flags flags)
16    : fFirstPageCount(0),
17      fNextObjNum(1),
18      fNextFirstPageObjNum(0),
19      fDocumentFlags(flags) {
20}
21
22SkPDFCatalog::~SkPDFCatalog() {
23    fSubstituteResourcesRemaining.safeUnrefAll();
24    fSubstituteResourcesFirstPage.safeUnrefAll();
25}
26
27SkPDFObject* SkPDFCatalog::addObject(SkPDFObject* obj, bool onFirstPage) {
28    if (findObjectIndex(obj) != -1) {  // object already added
29        return obj;
30    }
31    SkASSERT(fNextFirstPageObjNum == 0);
32    if (onFirstPage) {
33        fFirstPageCount++;
34    }
35
36    struct Rec newEntry(obj, onFirstPage);
37    fCatalog.append(1, &newEntry);
38    return obj;
39}
40
41size_t SkPDFCatalog::setFileOffset(SkPDFObject* obj, off_t offset) {
42    int objIndex = assignObjNum(obj) - 1;
43    SkASSERT(fCatalog[objIndex].fObjNumAssigned);
44    SkASSERT(fCatalog[objIndex].fFileOffset == 0);
45    fCatalog[objIndex].fFileOffset = offset;
46
47    return getSubstituteObject(obj)->getOutputSize(this, true);
48}
49
50void SkPDFCatalog::emitObjectNumber(SkWStream* stream, SkPDFObject* obj) {
51    stream->writeDecAsText(assignObjNum(obj));
52    stream->writeText(" 0");  // Generation number is always 0.
53}
54
55size_t SkPDFCatalog::getObjectNumberSize(SkPDFObject* obj) {
56    SkDynamicMemoryWStream buffer;
57    emitObjectNumber(&buffer, obj);
58    return buffer.getOffset();
59}
60
61int SkPDFCatalog::findObjectIndex(SkPDFObject* obj) const {
62    for (int i = 0; i < fCatalog.count(); i++) {
63        if (fCatalog[i].fObject == obj) {
64            return i;
65        }
66    }
67    // If it's not in the main array, check if it's a substitute object.
68    for (int i = 0; i < fSubstituteMap.count(); ++i) {
69        if (fSubstituteMap[i].fSubstitute == obj) {
70            return findObjectIndex(fSubstituteMap[i].fOriginal);
71        }
72    }
73    return -1;
74}
75
76int SkPDFCatalog::assignObjNum(SkPDFObject* obj) {
77    int pos = findObjectIndex(obj);
78    // If this assert fails, it means you probably forgot to add an object
79    // to the resource list.
80    SkASSERT(pos >= 0);
81    uint32_t currentIndex = pos;
82    if (fCatalog[currentIndex].fObjNumAssigned) {
83        return currentIndex + 1;
84    }
85
86    // First assignment.
87    if (fNextFirstPageObjNum == 0) {
88        fNextFirstPageObjNum = fCatalog.count() - fFirstPageCount + 1;
89    }
90
91    uint32_t objNum;
92    if (fCatalog[currentIndex].fOnFirstPage) {
93        objNum = fNextFirstPageObjNum;
94        fNextFirstPageObjNum++;
95    } else {
96        objNum = fNextObjNum;
97        fNextObjNum++;
98    }
99
100    // When we assign an object an object number, we put it in that array
101    // offset (minus 1 because object number 0 is reserved).
102    SkASSERT(!fCatalog[objNum - 1].fObjNumAssigned);
103    if (objNum - 1 != currentIndex) {
104        SkTSwap(fCatalog[objNum - 1], fCatalog[currentIndex]);
105    }
106    fCatalog[objNum - 1].fObjNumAssigned = true;
107    return objNum;
108}
109
110int32_t SkPDFCatalog::emitXrefTable(SkWStream* stream, bool firstPage) {
111    int first = -1;
112    int last = fCatalog.count() - 1;
113    // TODO(vandebo): Support linearized format.
114    // int last = fCatalog.count() - fFirstPageCount - 1;
115    // if (firstPage) {
116    //     first = fCatalog.count() - fFirstPageCount;
117    //     last = fCatalog.count() - 1;
118    // }
119
120    stream->writeText("xref\n");
121    stream->writeDecAsText(first + 1);
122    stream->writeText(" ");
123    stream->writeDecAsText(last - first + 1);
124    stream->writeText("\n");
125
126    if (first == -1) {
127        stream->writeText("0000000000 65535 f \n");
128        first++;
129    }
130    for (int i = first; i <= last; i++) {
131        // For 32 bits platforms, the maximum offset has to fit within off_t
132        // which is a 32 bits signed integer on these platforms.
133        SkDEBUGCODE(static const off_t kMaxOff = SK_MaxS32;)
134        SkASSERT(fCatalog[i].fFileOffset > 0);
135        SkASSERT(fCatalog[i].fFileOffset < kMaxOff);
136        stream->writeBigDecAsText(fCatalog[i].fFileOffset, 10);
137        stream->writeText(" 00000 n \n");
138    }
139
140    return fCatalog.count() + 1;
141}
142
143void SkPDFCatalog::setSubstitute(SkPDFObject* original,
144                                 SkPDFObject* substitute) {
145#if defined(SK_DEBUG)
146    // Sanity check: is the original already in substitute list?
147    for (int i = 0; i < fSubstituteMap.count(); ++i) {
148        if (original == fSubstituteMap[i].fSubstitute ||
149            original == fSubstituteMap[i].fOriginal) {
150            SkASSERT(false);
151            return;
152        }
153    }
154#endif
155    // Check if the original is on first page.
156    bool onFirstPage = false;
157    for (int i = 0; i < fCatalog.count(); ++i) {
158        if (fCatalog[i].fObject == original) {
159            onFirstPage = fCatalog[i].fOnFirstPage;
160            break;
161        }
162#if defined(SK_DEBUG)
163        if (i == fCatalog.count() - 1) {
164            SkASSERT(false);  // original not in catalog
165            return;
166        }
167#endif
168    }
169
170    SubstituteMapping newMapping(original, substitute);
171    fSubstituteMap.append(1, &newMapping);
172
173    // Add resource objects of substitute object to catalog.
174    SkTSet<SkPDFObject*>* targetSet = getSubstituteList(onFirstPage);
175    SkTSet<SkPDFObject*> newResourceObjects;
176    newMapping.fSubstitute->getResources(*targetSet, &newResourceObjects);
177    for (int i = 0; i < newResourceObjects.count(); ++i) {
178        addObject(newResourceObjects[i], onFirstPage);
179    }
180    // mergeInto returns the number of duplicates.
181    // If there are duplicates, there is a bug and we mess ref counting.
182    SkDEBUGCODE(int duplicates =) targetSet->mergeInto(newResourceObjects);
183    SkASSERT(duplicates == 0);
184}
185
186SkPDFObject* SkPDFCatalog::getSubstituteObject(SkPDFObject* object) {
187    for (int i = 0; i < fSubstituteMap.count(); ++i) {
188        if (object == fSubstituteMap[i].fOriginal) {
189            return fSubstituteMap[i].fSubstitute;
190        }
191    }
192    return object;
193}
194
195off_t SkPDFCatalog::setSubstituteResourcesOffsets(off_t fileOffset,
196                                                  bool firstPage) {
197    SkTSet<SkPDFObject*>* targetSet = getSubstituteList(firstPage);
198    off_t offsetSum = fileOffset;
199    for (int i = 0; i < targetSet->count(); ++i) {
200        offsetSum += setFileOffset((*targetSet)[i], offsetSum);
201    }
202    return offsetSum - fileOffset;
203}
204
205void SkPDFCatalog::emitSubstituteResources(SkWStream *stream, bool firstPage) {
206    SkTSet<SkPDFObject*>* targetSet = getSubstituteList(firstPage);
207    for (int i = 0; i < targetSet->count(); ++i) {
208        (*targetSet)[i]->emit(stream, this, true);
209    }
210}
211
212SkTSet<SkPDFObject*>* SkPDFCatalog::getSubstituteList(bool firstPage) {
213    return firstPage ? &fSubstituteResourcesFirstPage :
214                       &fSubstituteResourcesRemaining;
215}
216