| |
| /* |
| * 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 "SkDeflate.h" |
| #include "SkPDFTypes.h" |
| #include "SkPDFUtils.h" |
| #include "SkStreamPriv.h" |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| SkString* pun(char* x) { return reinterpret_cast<SkString*>(x); } |
| const SkString* pun(const char* x) { |
| return reinterpret_cast<const SkString*>(x); |
| } |
| |
| SkPDFUnion::SkPDFUnion(Type t) : fType(t) {} |
| |
| SkPDFUnion::~SkPDFUnion() { |
| switch (fType) { |
| case Type::kNameSkS: |
| case Type::kStringSkS: |
| pun(fSkString)->~SkString(); |
| return; |
| case Type::kObjRef: |
| case Type::kObject: |
| SkSafeUnref(fObject); |
| return; |
| default: |
| return; |
| } |
| } |
| |
| SkPDFUnion& SkPDFUnion::operator=(SkPDFUnion&& other) { |
| if (this != &other) { |
| this->~SkPDFUnion(); |
| new (this) SkPDFUnion(other.move()); |
| } |
| return *this; |
| } |
| |
| SkPDFUnion::SkPDFUnion(SkPDFUnion&& other) { |
| SkASSERT(this != &other); |
| memcpy(this, &other, sizeof(*this)); |
| other.fType = Type::kDestroyed; |
| } |
| |
| #if 0 |
| SkPDFUnion SkPDFUnion::copy() const { |
| SkPDFUnion u(fType); |
| memcpy(&u, this, sizeof(u)); |
| switch (fType) { |
| case Type::kNameSkS: |
| case Type::kStringSkS: |
| new (pun(u.fSkString)) SkString (*pun(fSkString)); |
| return u.move(); |
| case Type::kObjRef: |
| case Type::kObject: |
| SkRef(u.fObject); |
| return u.move(); |
| default: |
| return u.move(); |
| } |
| } |
| SkPDFUnion& SkPDFUnion::operator=(const SkPDFUnion& other) { |
| return *this = other.copy(); |
| } |
| SkPDFUnion::SkPDFUnion(const SkPDFUnion& other) { |
| *this = other.copy(); |
| } |
| #endif |
| |
| bool SkPDFUnion::isName() const { |
| return Type::kName == fType || Type::kNameSkS == fType; |
| } |
| |
| #ifdef SK_DEBUG |
| // Most names need no escaping. Such names are handled as static |
| // const strings. |
| bool is_valid_name(const char* n) { |
| static const char kControlChars[] = "/%()<>[]{}"; |
| while (*n) { |
| if (*n < '!' || *n > '~' || strchr(kControlChars, *n)) { |
| return false; |
| } |
| ++n; |
| } |
| return true; |
| } |
| #endif // SK_DEBUG |
| |
| // Given an arbitrary string, write it as a valid name (not including |
| // leading slash). |
| static void write_name_escaped(SkWStream* o, const char* name) { |
| static const char kToEscape[] = "#/%()<>[]{}"; |
| static const char kHex[] = "0123456789ABCDEF"; |
| for (const uint8_t* n = reinterpret_cast<const uint8_t*>(name); *n; ++n) { |
| if (*n < '!' || *n > '~' || strchr(kToEscape, *n)) { |
| char buffer[3] = {'#', '\0', '\0'}; |
| buffer[1] = kHex[(*n >> 4) & 0xF]; |
| buffer[2] = kHex[*n & 0xF]; |
| o->write(buffer, sizeof(buffer)); |
| } else { |
| o->write(n, 1); |
| } |
| } |
| } |
| |
| static void write_string(SkWStream* o, const SkString& s) { |
| o->write(s.c_str(), s.size()); |
| } |
| |
| static SkString format_string(const SkString& s) { |
| return SkPDFUtils::FormatString(s.c_str(), s.size()); |
| } |
| |
| static SkString format_string(const char* s) { |
| return SkPDFUtils::FormatString(s, strlen(s)); |
| } |
| |
| void SkPDFUnion::emitObject(SkWStream* stream, |
| const SkPDFObjNumMap& objNumMap, |
| const SkPDFSubstituteMap& substitutes) const { |
| switch (fType) { |
| case Type::kInt: |
| stream->writeDecAsText(fIntValue); |
| return; |
| case Type::kBool: |
| stream->writeText(fBoolValue ? "true" : "false"); |
| return; |
| case Type::kScalar: |
| SkPDFUtils::AppendScalar(fScalarValue, stream); |
| return; |
| case Type::kName: |
| stream->writeText("/"); |
| SkASSERT(is_valid_name(fStaticString)); |
| stream->writeText(fStaticString); |
| return; |
| case Type::kString: |
| SkASSERT(fStaticString); |
| write_string(stream, format_string(fStaticString)); |
| return; |
| case Type::kNameSkS: |
| stream->writeText("/"); |
| write_name_escaped(stream, pun(fSkString)->c_str()); |
| return; |
| case Type::kStringSkS: |
| write_string(stream, format_string(*pun(fSkString))); |
| return; |
| case Type::kObjRef: |
| stream->writeDecAsText(objNumMap.getObjectNumber( |
| substitutes.getSubstitute(fObject))); |
| stream->writeText(" 0 R"); // Generation number is always 0. |
| return; |
| case Type::kObject: |
| fObject->emitObject(stream, objNumMap, substitutes); |
| return; |
| default: |
| SkDEBUGFAIL("SkPDFUnion::emitObject with bad type"); |
| } |
| } |
| |
| void SkPDFUnion::addResources(SkPDFObjNumMap* objNumMap, |
| const SkPDFSubstituteMap& substituteMap) const { |
| switch (fType) { |
| case Type::kInt: |
| case Type::kBool: |
| case Type::kScalar: |
| case Type::kName: |
| case Type::kString: |
| case Type::kNameSkS: |
| case Type::kStringSkS: |
| return; // These have no resources. |
| case Type::kObjRef: { |
| SkPDFObject* obj = substituteMap.getSubstitute(fObject); |
| if (objNumMap->addObject(obj)) { |
| obj->addResources(objNumMap, substituteMap); |
| } |
| return; |
| } |
| case Type::kObject: |
| fObject->addResources(objNumMap, substituteMap); |
| return; |
| default: |
| SkDEBUGFAIL("SkPDFUnion::addResources with bad type"); |
| } |
| } |
| |
| SkPDFUnion SkPDFUnion::Int(int32_t value) { |
| SkPDFUnion u(Type::kInt); |
| u.fIntValue = value; |
| return u.move(); |
| } |
| |
| SkPDFUnion SkPDFUnion::Bool(bool value) { |
| SkPDFUnion u(Type::kBool); |
| u.fBoolValue = value; |
| return u.move(); |
| } |
| |
| SkPDFUnion SkPDFUnion::Scalar(SkScalar value) { |
| SkPDFUnion u(Type::kScalar); |
| u.fScalarValue = value; |
| return u.move(); |
| } |
| |
| SkPDFUnion SkPDFUnion::Name(const char* value) { |
| SkPDFUnion u(Type::kName); |
| SkASSERT(value); |
| SkASSERT(is_valid_name(value)); |
| u.fStaticString = value; |
| return u.move(); |
| } |
| |
| SkPDFUnion SkPDFUnion::String(const char* value) { |
| SkPDFUnion u(Type::kString); |
| SkASSERT(value); |
| u.fStaticString = value; |
| return u.move(); |
| } |
| |
| SkPDFUnion SkPDFUnion::Name(const SkString& s) { |
| SkPDFUnion u(Type::kNameSkS); |
| new (pun(u.fSkString)) SkString(s); |
| return u.move(); |
| } |
| |
| SkPDFUnion SkPDFUnion::String(const SkString& s) { |
| SkPDFUnion u(Type::kStringSkS); |
| new (pun(u.fSkString)) SkString(s); |
| return u.move(); |
| } |
| |
| SkPDFUnion SkPDFUnion::ObjRef(SkPDFObject* ptr) { |
| SkPDFUnion u(Type::kObjRef); |
| SkASSERT(ptr); |
| u.fObject = ptr; |
| return u.move(); |
| } |
| |
| SkPDFUnion SkPDFUnion::Object(SkPDFObject* ptr) { |
| SkPDFUnion u(Type::kObject); |
| SkASSERT(ptr); |
| u.fObject = ptr; |
| return u.move(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| #if 0 // Enable if needed. |
| void SkPDFAtom::emitObject(SkWStream* stream, |
| const SkPDFObjNumMap& objNumMap, |
| const SkPDFSubstituteMap& substitutes) const { |
| fValue.emitObject(stream, objNumMap, substitutes); |
| } |
| void SkPDFAtom::addResources(SkPDFObjNumMap* map, |
| const SkPDFSubstituteMap& substitutes) const { |
| fValue.addResources(map, substitutes); |
| } |
| #endif // 0 |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| SkPDFArray::SkPDFArray() {} |
| SkPDFArray::~SkPDFArray() { |
| for (SkPDFUnion& value : fValues) { |
| value.~SkPDFUnion(); |
| } |
| fValues.reset(); |
| } |
| |
| int SkPDFArray::size() const { return fValues.count(); } |
| |
| void SkPDFArray::reserve(int length) { fValues.setReserve(length); } |
| |
| void SkPDFArray::emitObject(SkWStream* stream, |
| const SkPDFObjNumMap& objNumMap, |
| const SkPDFSubstituteMap& substitutes) const { |
| stream->writeText("["); |
| for (int i = 0; i < fValues.count(); i++) { |
| fValues[i].emitObject(stream, objNumMap, substitutes); |
| if (i + 1 < fValues.count()) { |
| stream->writeText(" "); |
| } |
| } |
| stream->writeText("]"); |
| } |
| |
| void SkPDFArray::addResources(SkPDFObjNumMap* catalog, |
| const SkPDFSubstituteMap& substitutes) const { |
| for (const SkPDFUnion& value : fValues) { |
| value.addResources(catalog, substitutes); |
| } |
| } |
| |
| void SkPDFArray::append(SkPDFUnion&& value) { new (fValues.append()) SkPDFUnion(value.move()); } |
| |
| void SkPDFArray::appendInt(int32_t value) { |
| this->append(SkPDFUnion::Int(value)); |
| } |
| |
| void SkPDFArray::appendBool(bool value) { |
| this->append(SkPDFUnion::Bool(value)); |
| } |
| |
| void SkPDFArray::appendScalar(SkScalar value) { |
| this->append(SkPDFUnion::Scalar(value)); |
| } |
| |
| void SkPDFArray::appendName(const char name[]) { |
| this->append(SkPDFUnion::Name(SkString(name))); |
| } |
| |
| void SkPDFArray::appendName(const SkString& name) { |
| this->append(SkPDFUnion::Name(name)); |
| } |
| |
| void SkPDFArray::appendString(const SkString& value) { |
| this->append(SkPDFUnion::String(value)); |
| } |
| |
| void SkPDFArray::appendString(const char value[]) { |
| this->append(SkPDFUnion::String(value)); |
| } |
| |
| void SkPDFArray::appendObject(SkPDFObject* value) { |
| this->append(SkPDFUnion::Object(value)); |
| } |
| |
| void SkPDFArray::appendObjRef(SkPDFObject* value) { |
| this->append(SkPDFUnion::ObjRef(value)); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| SkPDFDict::SkPDFDict() {} |
| |
| SkPDFDict::~SkPDFDict() { this->clear(); } |
| |
| SkPDFDict::SkPDFDict(const char type[]) { this->insertName("Type", type); } |
| |
| void SkPDFDict::emitObject(SkWStream* stream, |
| const SkPDFObjNumMap& objNumMap, |
| const SkPDFSubstituteMap& substitutes) const { |
| stream->writeText("<<"); |
| this->emitAll(stream, objNumMap, substitutes); |
| stream->writeText(">>"); |
| } |
| |
| void SkPDFDict::emitAll(SkWStream* stream, |
| const SkPDFObjNumMap& objNumMap, |
| const SkPDFSubstituteMap& substitutes) const { |
| for (int i = 0; i < fRecords.count(); i++) { |
| fRecords[i].fKey.emitObject(stream, objNumMap, substitutes); |
| stream->writeText(" "); |
| fRecords[i].fValue.emitObject(stream, objNumMap, substitutes); |
| if (i + 1 < fRecords.count()) { |
| stream->writeText("\n"); |
| } |
| } |
| } |
| |
| void SkPDFDict::addResources(SkPDFObjNumMap* catalog, |
| const SkPDFSubstituteMap& substitutes) const { |
| for (int i = 0; i < fRecords.count(); i++) { |
| fRecords[i].fKey.addResources(catalog, substitutes); |
| fRecords[i].fValue.addResources(catalog, substitutes); |
| } |
| } |
| |
| void SkPDFDict::set(SkPDFUnion&& name, SkPDFUnion&& value) { |
| Record* rec = fRecords.append(); |
| SkASSERT(name.isName()); |
| new (&rec->fKey) SkPDFUnion(name.move()); |
| new (&rec->fValue) SkPDFUnion(value.move()); |
| } |
| |
| int SkPDFDict::size() const { return fRecords.count(); } |
| |
| void SkPDFDict::insertObjRef(const char key[], SkPDFObject* value) { |
| this->set(SkPDFUnion::Name(key), SkPDFUnion::ObjRef(value)); |
| } |
| void SkPDFDict::insertObjRef(const SkString& key, SkPDFObject* value) { |
| this->set(SkPDFUnion::Name(key), SkPDFUnion::ObjRef(value)); |
| } |
| |
| void SkPDFDict::insertObject(const char key[], SkPDFObject* value) { |
| this->set(SkPDFUnion::Name(key), SkPDFUnion::Object(value)); |
| } |
| void SkPDFDict::insertObject(const SkString& key, SkPDFObject* value) { |
| this->set(SkPDFUnion::Name(key), SkPDFUnion::Object(value)); |
| } |
| |
| void SkPDFDict::insertBool(const char key[], bool value) { |
| this->set(SkPDFUnion::Name(key), SkPDFUnion::Bool(value)); |
| } |
| |
| void SkPDFDict::insertInt(const char key[], int32_t value) { |
| this->set(SkPDFUnion::Name(key), SkPDFUnion::Int(value)); |
| } |
| |
| void SkPDFDict::insertInt(const char key[], size_t value) { |
| this->insertInt(key, SkToS32(value)); |
| } |
| |
| void SkPDFDict::insertScalar(const char key[], SkScalar value) { |
| this->set(SkPDFUnion::Name(key), SkPDFUnion::Scalar(value)); |
| } |
| |
| void SkPDFDict::insertName(const char key[], const char name[]) { |
| this->set(SkPDFUnion::Name(key), SkPDFUnion::Name(name)); |
| } |
| |
| void SkPDFDict::insertName(const char key[], const SkString& name) { |
| this->set(SkPDFUnion::Name(key), SkPDFUnion::Name(name)); |
| } |
| |
| void SkPDFDict::insertString(const char key[], const char value[]) { |
| this->set(SkPDFUnion::Name(key), SkPDFUnion::String(value)); |
| } |
| |
| void SkPDFDict::insertString(const char key[], const SkString& value) { |
| this->set(SkPDFUnion::Name(key), SkPDFUnion::String(value)); |
| } |
| |
| void SkPDFDict::clear() { |
| for (Record& rec : fRecords) { |
| rec.fKey.~SkPDFUnion(); |
| rec.fValue.~SkPDFUnion(); |
| } |
| fRecords.reset(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| void SkPDFSharedStream::emitObject( |
| SkWStream* stream, |
| const SkPDFObjNumMap& objNumMap, |
| const SkPDFSubstituteMap& substitutes) const { |
| SkDynamicMemoryWStream buffer; |
| SkDeflateWStream deflateWStream(&buffer); |
| // Since emitObject is const, this function doesn't change the dictionary. |
| SkAutoTDelete<SkStreamAsset> dup(fAsset->duplicate()); // Cheap copy |
| SkASSERT(dup); |
| SkStreamCopy(&deflateWStream, dup.get()); |
| deflateWStream.finalize(); |
| size_t length = buffer.bytesWritten(); |
| stream->writeText("<<"); |
| fDict->emitAll(stream, objNumMap, substitutes); |
| stream->writeText("\n"); |
| SkPDFUnion::Name("Length").emitObject(stream, objNumMap, substitutes); |
| stream->writeText(" "); |
| SkPDFUnion::Int(length).emitObject(stream, objNumMap, substitutes); |
| stream->writeText("\n"); |
| SkPDFUnion::Name("Filter").emitObject(stream, objNumMap, substitutes); |
| stream->writeText(" "); |
| SkPDFUnion::Name("FlateDecode").emitObject(stream, objNumMap, substitutes); |
| stream->writeText(">>"); |
| stream->writeText(" stream\n"); |
| buffer.writeToStream(stream); |
| stream->writeText("\nendstream"); |
| } |
| |
| void SkPDFSharedStream::addResources( |
| SkPDFObjNumMap* catalog, const SkPDFSubstituteMap& substitutes) const { |
| fDict->addResources(catalog, substitutes); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| SkPDFSubstituteMap::~SkPDFSubstituteMap() { |
| fSubstituteMap.foreach( |
| [](SkPDFObject*, SkPDFObject** v) { (*v)->unref(); }); |
| } |
| |
| void SkPDFSubstituteMap::setSubstitute(SkPDFObject* original, |
| SkPDFObject* substitute) { |
| SkASSERT(original != substitute); |
| SkASSERT(!fSubstituteMap.find(original)); |
| fSubstituteMap.set(original, SkRef(substitute)); |
| } |
| |
| SkPDFObject* SkPDFSubstituteMap::getSubstitute(SkPDFObject* object) const { |
| SkPDFObject** found = fSubstituteMap.find(object); |
| return found ? *found : object; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| bool SkPDFObjNumMap::addObject(SkPDFObject* obj) { |
| if (fObjectNumbers.find(obj)) { |
| return false; |
| } |
| fObjectNumbers.set(obj, fObjectNumbers.count() + 1); |
| fObjects.push(obj); |
| return true; |
| } |
| |
| int32_t SkPDFObjNumMap::getObjectNumber(SkPDFObject* obj) const { |
| int32_t* objectNumberFound = fObjectNumbers.find(obj); |
| SkASSERT(objectNumberFound); |
| return *objectNumberFound; |
| } |
| |