Use Google3 style guide with .clang-format
Test: style change only, builds ok
Change-Id: I885180e24cb2e7b58cfb4967c3bcb40058ce4078
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index dbd8062..d001228 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -33,8 +33,8 @@
#include "xml/XmlDom.h"
#include "xml/XmlPullParser.h"
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <android-base/errors.h>
#include <android-base/file.h>
@@ -48,16 +48,18 @@
namespace aapt {
struct ResourcePathData {
- Source source;
- std::string resourceDir;
- std::string name;
- std::string extension;
+ Source source;
+ std::string resourceDir;
+ std::string name;
+ std::string extension;
- // Original config str. We keep this because when we parse the config, we may add on
- // version qualifiers. We want to preserve the original input so the output is easily
- // computed before hand.
- std::string configStr;
- ConfigDescription config;
+ // Original config str. We keep this because when we parse the config, we may
+ // add on
+ // version qualifiers. We want to preserve the original input so the output is
+ // easily
+ // computed before hand.
+ std::string configStr;
+ ConfigDescription config;
};
/**
@@ -66,696 +68,735 @@
*/
static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
std::string* outError) {
- std::vector<std::string> parts = util::split(path, file::sDirSep);
- if (parts.size() < 2) {
- if (outError) *outError = "bad resource path";
- return {};
+ std::vector<std::string> parts = util::split(path, file::sDirSep);
+ if (parts.size() < 2) {
+ if (outError) *outError = "bad resource path";
+ return {};
+ }
+
+ std::string& dir = parts[parts.size() - 2];
+ StringPiece dirStr = dir;
+
+ StringPiece configStr;
+ ConfigDescription config;
+ size_t dashPos = dir.find('-');
+ if (dashPos != std::string::npos) {
+ configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
+ if (!ConfigDescription::parse(configStr, &config)) {
+ if (outError) {
+ std::stringstream errStr;
+ errStr << "invalid configuration '" << configStr << "'";
+ *outError = errStr.str();
+ }
+ return {};
}
+ dirStr = dirStr.substr(0, dashPos);
+ }
- std::string& dir = parts[parts.size() - 2];
- StringPiece dirStr = dir;
+ std::string& filename = parts[parts.size() - 1];
+ StringPiece name = filename;
+ StringPiece extension;
+ size_t dotPos = filename.find('.');
+ if (dotPos != std::string::npos) {
+ extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
+ name = name.substr(0, dotPos);
+ }
- StringPiece configStr;
- ConfigDescription config;
- size_t dashPos = dir.find('-');
- if (dashPos != std::string::npos) {
- configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
- if (!ConfigDescription::parse(configStr, &config)) {
- if (outError) {
- std::stringstream errStr;
- errStr << "invalid configuration '" << configStr << "'";
- *outError = errStr.str();
- }
- return {};
- }
- dirStr = dirStr.substr(0, dashPos);
- }
-
- std::string& filename = parts[parts.size() - 1];
- StringPiece name = filename;
- StringPiece extension;
- size_t dotPos = filename.find('.');
- if (dotPos != std::string::npos) {
- extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
- name = name.substr(0, dotPos);
- }
-
- return ResourcePathData{
- Source(path),
- dirStr.toString(),
- name.toString(),
- extension.toString(),
- configStr.toString(),
- config
- };
+ return ResourcePathData{Source(path), dirStr.toString(),
+ name.toString(), extension.toString(),
+ configStr.toString(), config};
}
struct CompileOptions {
- std::string outputPath;
- Maybe<std::string> resDir;
- bool pseudolocalize = false;
- bool legacyMode = false;
- bool verbose = false;
+ std::string outputPath;
+ Maybe<std::string> resDir;
+ bool pseudolocalize = false;
+ bool legacyMode = false;
+ bool verbose = false;
};
static std::string buildIntermediateFilename(const ResourcePathData& data) {
- std::stringstream name;
- name << data.resourceDir;
- if (!data.configStr.empty()) {
- name << "-" << data.configStr;
- }
- name << "_" << data.name;
- if (!data.extension.empty()) {
- name << "." << data.extension;
- }
- name << ".flat";
- return name.str();
+ std::stringstream name;
+ name << data.resourceDir;
+ if (!data.configStr.empty()) {
+ name << "-" << data.configStr;
+ }
+ name << "_" << data.name;
+ if (!data.extension.empty()) {
+ name << "." << data.extension;
+ }
+ name << ".flat";
+ return name.str();
}
static bool isHidden(const StringPiece& filename) {
- return util::stringStartsWith(filename, ".");
+ return util::stringStartsWith(filename, ".");
}
/**
* Walks the res directory structure, looking for resource files.
*/
-static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
+static bool loadInputFilesFromDir(IAaptContext* context,
+ const CompileOptions& options,
std::vector<ResourcePathData>* outPathData) {
- const std::string& rootDir = options.resDir.value();
- std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir);
- if (!d) {
- context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ const std::string& rootDir = options.resDir.value();
+ std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()),
+ closedir);
+ if (!d) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* entry = readdir(d.get())) {
+ if (isHidden(entry->d_name)) {
+ continue;
+ }
+
+ std::string prefixPath = rootDir;
+ file::appendPath(&prefixPath, entry->d_name);
+
+ if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
+ continue;
+ }
+
+ std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()),
+ closedir);
+ if (!subDir) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* leafEntry = readdir(subDir.get())) {
+ if (isHidden(leafEntry->d_name)) {
+ continue;
+ }
+
+ std::string fullPath = prefixPath;
+ file::appendPath(&fullPath, leafEntry->d_name);
+
+ std::string errStr;
+ Maybe<ResourcePathData> pathData =
+ extractResourcePathData(fullPath, &errStr);
+ if (!pathData) {
+ context->getDiagnostics()->error(DiagMessage() << errStr);
return false;
+ }
+
+ outPathData->push_back(std::move(pathData.value()));
}
-
- while (struct dirent* entry = readdir(d.get())) {
- if (isHidden(entry->d_name)) {
- continue;
- }
-
- std::string prefixPath = rootDir;
- file::appendPath(&prefixPath, entry->d_name);
-
- if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
- continue;
- }
-
- std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir);
- if (!subDir) {
- context->getDiagnostics()->error(DiagMessage() << strerror(errno));
- return false;
- }
-
- while (struct dirent* leafEntry = readdir(subDir.get())) {
- if (isHidden(leafEntry->d_name)) {
- continue;
- }
-
- std::string fullPath = prefixPath;
- file::appendPath(&fullPath, leafEntry->d_name);
-
- std::string errStr;
- Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr);
- if (!pathData) {
- context->getDiagnostics()->error(DiagMessage() << errStr);
- return false;
- }
-
- outPathData->push_back(std::move(pathData.value()));
- }
- }
- return true;
+ }
+ return true;
}
static bool compileTable(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, IArchiveWriter* writer,
+ const ResourcePathData& pathData,
+ IArchiveWriter* writer,
const std::string& outputPath) {
- ResourceTable table;
- {
- std::ifstream fin(pathData.source.path, std::ifstream::binary);
- if (!fin) {
- context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
- return false;
- }
-
-
- // Parse the values file from XML.
- xml::XmlPullParser xmlParser(fin);
-
- ResourceParserOptions parserOptions;
- parserOptions.errorOnPositionalArguments = !options.legacyMode;
-
- // If the filename includes donottranslate, then the default translatable is false.
- parserOptions.translatable = pathData.name.find("donottranslate") == std::string::npos;
-
- ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
- pathData.config, parserOptions);
- if (!resParser.parse(&xmlParser)) {
- return false;
- }
-
- fin.close();
+ ResourceTable table;
+ {
+ std::ifstream fin(pathData.source.path, std::ifstream::binary);
+ if (!fin) {
+ context->getDiagnostics()->error(DiagMessage(pathData.source)
+ << strerror(errno));
+ return false;
}
- if (options.pseudolocalize) {
- // Generate pseudo-localized strings (en-XA and ar-XB).
- // These are created as weak symbols, and are only generated from default configuration
- // strings and plurals.
- PseudolocaleGenerator pseudolocaleGenerator;
- if (!pseudolocaleGenerator.consume(context, &table)) {
- return false;
- }
+ // Parse the values file from XML.
+ xml::XmlPullParser xmlParser(fin);
+
+ ResourceParserOptions parserOptions;
+ parserOptions.errorOnPositionalArguments = !options.legacyMode;
+
+ // If the filename includes donottranslate, then the default translatable is
+ // false.
+ parserOptions.translatable =
+ pathData.name.find("donottranslate") == std::string::npos;
+
+ ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
+ pathData.config, parserOptions);
+ if (!resParser.parse(&xmlParser)) {
+ return false;
}
- // Ensure we have the compilation package at least.
- table.createPackage(context->getCompilationPackage());
+ fin.close();
+ }
- // Assign an ID to any package that has resources.
- for (auto& pkg : table.packages) {
- if (!pkg->id) {
- // If no package ID was set while parsing (public identifiers), auto assign an ID.
- pkg->id = context->getPackageId();
- }
+ if (options.pseudolocalize) {
+ // Generate pseudo-localized strings (en-XA and ar-XB).
+ // These are created as weak symbols, and are only generated from default
+ // configuration
+ // strings and plurals.
+ PseudolocaleGenerator pseudolocaleGenerator;
+ if (!pseudolocaleGenerator.consume(context, &table)) {
+ return false;
}
+ }
- // Create the file/zip entry.
- if (!writer->startEntry(outputPath, 0)) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
- return false;
+ // Ensure we have the compilation package at least.
+ table.createPackage(context->getCompilationPackage());
+
+ // Assign an ID to any package that has resources.
+ for (auto& pkg : table.packages) {
+ if (!pkg->id) {
+ // If no package ID was set while parsing (public identifiers), auto
+ // assign an ID.
+ pkg->id = context->getPackageId();
}
+ }
- // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
- // interface.
- CopyingOutputStreamAdaptor copyingAdaptor(writer);
+ // Create the file/zip entry.
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to open");
+ return false;
+ }
- std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table);
- if (!pbTable->SerializeToZeroCopyStream(©ingAdaptor)) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
- return false;
- }
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->finishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor copyingAdaptor(writer);
+
+ std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table);
+ if (!pbTable->SerializeToZeroCopyStream(©ingAdaptor)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to write");
+ return false;
}
+ }
- if (!writer->finishEntry()) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to finish entry");
- return false;
- }
- return true;
+ if (!writer->finishEntry()) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to finish entry");
+ return false;
+ }
+ return true;
}
-static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const ResourceFile& file,
- const BigBuffer& buffer, IArchiveWriter* writer,
+static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath,
+ const ResourceFile& file,
+ const BigBuffer& buffer,
+ IArchiveWriter* writer,
IDiagnostics* diag) {
- // Start the entry so we can write the header.
- if (!writer->startEntry(outputPath, 0)) {
- diag->error(DiagMessage(outputPath) << "failed to open file");
- return false;
+ // Start the entry so we can write the header.
+ if (!writer->startEntry(outputPath, 0)) {
+ diag->error(DiagMessage(outputPath) << "failed to open file");
+ return false;
+ }
+
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->finishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor copyingAdaptor(writer);
+ CompiledFileOutputStream outputStream(©ingAdaptor);
+
+ // Number of CompiledFiles.
+ outputStream.WriteLittleEndian32(1);
+
+ std::unique_ptr<pb::CompiledFile> compiledFile =
+ serializeCompiledFileToPb(file);
+ outputStream.WriteCompiledFile(compiledFile.get());
+ outputStream.WriteData(&buffer);
+
+ if (outputStream.HadError()) {
+ diag->error(DiagMessage(outputPath) << "failed to write data");
+ return false;
}
+ }
- // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
- // interface.
- CopyingOutputStreamAdaptor copyingAdaptor(writer);
- CompiledFileOutputStream outputStream(©ingAdaptor);
-
- // Number of CompiledFiles.
- outputStream.WriteLittleEndian32(1);
-
- std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file);
- outputStream.WriteCompiledFile(compiledFile.get());
- outputStream.WriteData(&buffer);
-
- if (outputStream.HadError()) {
- diag->error(DiagMessage(outputPath) << "failed to write data");
- return false;
- }
- }
-
- if (!writer->finishEntry()) {
- diag->error(DiagMessage(outputPath) << "failed to finish writing data");
- return false;
- }
- return true;
+ if (!writer->finishEntry()) {
+ diag->error(DiagMessage(outputPath) << "failed to finish writing data");
+ return false;
+ }
+ return true;
}
-static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const ResourceFile& file,
- const android::FileMap& map, IArchiveWriter* writer,
+static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath,
+ const ResourceFile& file,
+ const android::FileMap& map,
+ IArchiveWriter* writer,
IDiagnostics* diag) {
- // Start the entry so we can write the header.
- if (!writer->startEntry(outputPath, 0)) {
- diag->error(DiagMessage(outputPath) << "failed to open file");
- return false;
+ // Start the entry so we can write the header.
+ if (!writer->startEntry(outputPath, 0)) {
+ diag->error(DiagMessage(outputPath) << "failed to open file");
+ return false;
+ }
+
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->finishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor copyingAdaptor(writer);
+ CompiledFileOutputStream outputStream(©ingAdaptor);
+
+ // Number of CompiledFiles.
+ outputStream.WriteLittleEndian32(1);
+
+ std::unique_ptr<pb::CompiledFile> compiledFile =
+ serializeCompiledFileToPb(file);
+ outputStream.WriteCompiledFile(compiledFile.get());
+ outputStream.WriteData(map.getDataPtr(), map.getDataLength());
+
+ if (outputStream.HadError()) {
+ diag->error(DiagMessage(outputPath) << "failed to write data");
+ return false;
}
+ }
- // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
- // interface.
- CopyingOutputStreamAdaptor copyingAdaptor(writer);
- CompiledFileOutputStream outputStream(©ingAdaptor);
-
- // Number of CompiledFiles.
- outputStream.WriteLittleEndian32(1);
-
- std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file);
- outputStream.WriteCompiledFile(compiledFile.get());
- outputStream.WriteData(map.getDataPtr(), map.getDataLength());
-
- if (outputStream.HadError()) {
- diag->error(DiagMessage(outputPath) << "failed to write data");
- return false;
- }
- }
-
- if (!writer->finishEntry()) {
- diag->error(DiagMessage(outputPath) << "failed to finish writing data");
- return false;
- }
- return true;
+ if (!writer->finishEntry()) {
+ diag->error(DiagMessage(outputPath) << "failed to finish writing data");
+ return false;
+ }
+ return true;
}
-static bool flattenXmlToOutStream(IAaptContext* context, const StringPiece& outputPath,
+static bool flattenXmlToOutStream(IAaptContext* context,
+ const StringPiece& outputPath,
xml::XmlResource* xmlRes,
CompiledFileOutputStream* out) {
- BigBuffer buffer(1024);
- XmlFlattenerOptions xmlFlattenerOptions;
- xmlFlattenerOptions.keepRawValues = true;
- XmlFlattener flattener(&buffer, xmlFlattenerOptions);
- if (!flattener.consume(context, xmlRes)) {
- return false;
- }
+ BigBuffer buffer(1024);
+ XmlFlattenerOptions xmlFlattenerOptions;
+ xmlFlattenerOptions.keepRawValues = true;
+ XmlFlattener flattener(&buffer, xmlFlattenerOptions);
+ if (!flattener.consume(context, xmlRes)) {
+ return false;
+ }
- std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(xmlRes->file);
- out->WriteCompiledFile(pbCompiledFile.get());
- out->WriteData(&buffer);
+ std::unique_ptr<pb::CompiledFile> pbCompiledFile =
+ serializeCompiledFileToPb(xmlRes->file);
+ out->WriteCompiledFile(pbCompiledFile.get());
+ out->WriteData(&buffer);
- if (out->HadError()) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write data");
- return false;
- }
- return true;
+ if (out->HadError()) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to write data");
+ return false;
+ }
+ return true;
}
static bool compileXml(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& pathData, IArchiveWriter* writer,
const std::string& outputPath) {
- if (context->verbose()) {
- context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling XML");
+ if (context->verbose()) {
+ context->getDiagnostics()->note(DiagMessage(pathData.source)
+ << "compiling XML");
+ }
+
+ std::unique_ptr<xml::XmlResource> xmlRes;
+ {
+ std::ifstream fin(pathData.source.path, std::ifstream::binary);
+ if (!fin) {
+ context->getDiagnostics()->error(DiagMessage(pathData.source)
+ << strerror(errno));
+ return false;
}
- std::unique_ptr<xml::XmlResource> xmlRes;
- {
- std::ifstream fin(pathData.source.path, std::ifstream::binary);
- if (!fin) {
- context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
- return false;
- }
+ xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
- xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
+ fin.close();
+ }
- fin.close();
+ if (!xmlRes) {
+ return false;
+ }
+
+ xmlRes->file.name =
+ ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
+ xmlRes->file.config = pathData.config;
+ xmlRes->file.source = pathData.source;
+
+ // Collect IDs that are defined here.
+ XmlIdCollector collector;
+ if (!collector.consume(context, xmlRes.get())) {
+ return false;
+ }
+
+ // Look for and process any <aapt:attr> tags and create sub-documents.
+ InlineXmlFormatParser inlineXmlFormatParser;
+ if (!inlineXmlFormatParser.consume(context, xmlRes.get())) {
+ return false;
+ }
+
+ // Start the entry so we can write the header.
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to open file");
+ return false;
+ }
+
+ // Make sure CopyingOutputStreamAdaptor is deleted before we call
+ // writer->finishEntry().
+ {
+ // Wrap our IArchiveWriter with an adaptor that implements the
+ // ZeroCopyOutputStream
+ // interface.
+ CopyingOutputStreamAdaptor copyingAdaptor(writer);
+ CompiledFileOutputStream outputStream(©ingAdaptor);
+
+ std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments =
+ inlineXmlFormatParser.getExtractedInlineXmlDocuments();
+
+ // Number of CompiledFiles.
+ outputStream.WriteLittleEndian32(1 + inlineDocuments.size());
+
+ if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(),
+ &outputStream)) {
+ return false;
}
- if (!xmlRes) {
+ for (auto& inlineXmlDoc : inlineDocuments) {
+ if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(),
+ &outputStream)) {
return false;
+ }
}
+ }
- xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
- xmlRes->file.config = pathData.config;
- xmlRes->file.source = pathData.source;
-
- // Collect IDs that are defined here.
- XmlIdCollector collector;
- if (!collector.consume(context, xmlRes.get())) {
- return false;
- }
-
- // Look for and process any <aapt:attr> tags and create sub-documents.
- InlineXmlFormatParser inlineXmlFormatParser;
- if (!inlineXmlFormatParser.consume(context, xmlRes.get())) {
- return false;
- }
-
- // Start the entry so we can write the header.
- if (!writer->startEntry(outputPath, 0)) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open file");
- return false;
- }
-
- // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
- {
- // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
- // interface.
- CopyingOutputStreamAdaptor copyingAdaptor(writer);
- CompiledFileOutputStream outputStream(©ingAdaptor);
-
- std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments =
- inlineXmlFormatParser.getExtractedInlineXmlDocuments();
-
- // Number of CompiledFiles.
- outputStream.WriteLittleEndian32(1 + inlineDocuments.size());
-
- if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(), &outputStream)) {
- return false;
- }
-
- for (auto& inlineXmlDoc : inlineDocuments) {
- if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(), &outputStream)) {
- return false;
- }
- }
- }
-
- if (!writer->finishEntry()) {
- context->getDiagnostics()->error(DiagMessage(outputPath)
- << "failed to finish writing data");
- return false;
- }
- return true;
+ if (!writer->finishEntry()) {
+ context->getDiagnostics()->error(DiagMessage(outputPath)
+ << "failed to finish writing data");
+ return false;
+ }
+ return true;
}
class BigBufferOutputStream : public io::OutputStream {
-public:
- explicit BigBufferOutputStream(BigBuffer* buffer) : mBuffer(buffer) {
- }
+ public:
+ explicit BigBufferOutputStream(BigBuffer* buffer) : mBuffer(buffer) {}
- bool Next(void** data, int* len) override {
- size_t count;
- *data = mBuffer->nextBlock(&count);
- *len = static_cast<int>(count);
- return true;
- }
+ bool Next(void** data, int* len) override {
+ size_t count;
+ *data = mBuffer->nextBlock(&count);
+ *len = static_cast<int>(count);
+ return true;
+ }
- void BackUp(int count) override {
- mBuffer->backUp(count);
- }
+ void BackUp(int count) override { mBuffer->backUp(count); }
- int64_t ByteCount() const override {
- return mBuffer->size();
- }
+ int64_t ByteCount() const override { return mBuffer->size(); }
- bool HadError() const override {
- return false;
- }
+ bool HadError() const override { return false; }
-private:
- BigBuffer* mBuffer;
+ private:
+ BigBuffer* mBuffer;
- DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+ DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
};
static bool compilePng(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& pathData, IArchiveWriter* writer,
const std::string& outputPath) {
- if (context->verbose()) {
- context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling PNG");
+ if (context->verbose()) {
+ context->getDiagnostics()->note(DiagMessage(pathData.source)
+ << "compiling PNG");
+ }
+
+ BigBuffer buffer(4096);
+ ResourceFile resFile;
+ resFile.name =
+ ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
+ resFile.config = pathData.config;
+ resFile.source = pathData.source;
+
+ {
+ std::string content;
+ if (!android::base::ReadFileToString(pathData.source.path, &content)) {
+ context->getDiagnostics()->error(
+ DiagMessage(pathData.source)
+ << android::base::SystemErrorCodeToString(errno));
+ return false;
}
- BigBuffer buffer(4096);
- ResourceFile resFile;
- resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
- resFile.config = pathData.config;
- resFile.source = pathData.source;
+ BigBuffer crunchedPngBuffer(4096);
+ BigBufferOutputStream crunchedPngBufferOut(&crunchedPngBuffer);
- {
- std::string content;
- if (!android::base::ReadFileToString(pathData.source.path, &content)) {
- context->getDiagnostics()->error(DiagMessage(pathData.source)
- << android::base::SystemErrorCodeToString(errno));
- return false;
- }
-
- BigBuffer crunchedPngBuffer(4096);
- BigBufferOutputStream crunchedPngBufferOut(&crunchedPngBuffer);
-
- // Ensure that we only keep the chunks we care about if we end up
- // using the original PNG instead of the crunched one.
- PngChunkFilter pngChunkFilter(content);
- std::unique_ptr<Image> image = readPng(context, &pngChunkFilter);
- if (!image) {
- return false;
- }
-
- std::unique_ptr<NinePatch> ninePatch;
- if (pathData.extension == "9.png") {
- std::string err;
- ninePatch = NinePatch::create(image->rows.get(), image->width, image->height, &err);
- if (!ninePatch) {
- context->getDiagnostics()->error(DiagMessage() << err);
- return false;
- }
-
- // Remove the 1px border around the NinePatch.
- // Basically the row array is shifted up by 1, and the length is treated
- // as height - 2.
- // For each row, shift the array to the left by 1, and treat the length as width - 2.
- image->width -= 2;
- image->height -= 2;
- memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
- for (int32_t h = 0; h < image->height; h++) {
- memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
- }
-
- if (context->verbose()) {
- context->getDiagnostics()->note(DiagMessage(pathData.source)
- << "9-patch: " << *ninePatch);
- }
- }
-
- // Write the crunched PNG.
- if (!writePng(context, image.get(), ninePatch.get(), &crunchedPngBufferOut, {})) {
- return false;
- }
-
- if (ninePatch != nullptr
- || crunchedPngBufferOut.ByteCount() <= pngChunkFilter.ByteCount()) {
- // No matter what, we must use the re-encoded PNG, even if it is larger.
- // 9-patch images must be re-encoded since their borders are stripped.
- buffer.appendBuffer(std::move(crunchedPngBuffer));
- } else {
- // The re-encoded PNG is larger than the original, and there is
- // no mandatory transformation. Use the original.
- if (context->verbose()) {
- context->getDiagnostics()->note(DiagMessage(pathData.source)
- << "original PNG is smaller than crunched PNG"
- << ", using original");
- }
-
- PngChunkFilter pngChunkFilterAgain(content);
- BigBuffer filteredPngBuffer(4096);
- BigBufferOutputStream filteredPngBufferOut(&filteredPngBuffer);
- io::copy(&filteredPngBufferOut, &pngChunkFilterAgain);
- buffer.appendBuffer(std::move(filteredPngBuffer));
- }
-
- if (context->verbose()) {
- // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
- // This will help catch exotic cases where the new code may generate larger PNGs.
- std::stringstream legacyStream(content);
- BigBuffer legacyBuffer(4096);
- Png png(context->getDiagnostics());
- if (!png.process(pathData.source, &legacyStream, &legacyBuffer, {})) {
- return false;
- }
-
- context->getDiagnostics()->note(DiagMessage(pathData.source)
- << "legacy=" << legacyBuffer.size()
- << " new=" << buffer.size());
- }
+ // Ensure that we only keep the chunks we care about if we end up
+ // using the original PNG instead of the crunched one.
+ PngChunkFilter pngChunkFilter(content);
+ std::unique_ptr<Image> image = readPng(context, &pngChunkFilter);
+ if (!image) {
+ return false;
}
- if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
- context->getDiagnostics())) {
+ std::unique_ptr<NinePatch> ninePatch;
+ if (pathData.extension == "9.png") {
+ std::string err;
+ ninePatch = NinePatch::create(image->rows.get(), image->width,
+ image->height, &err);
+ if (!ninePatch) {
+ context->getDiagnostics()->error(DiagMessage() << err);
return false;
+ }
+
+ // Remove the 1px border around the NinePatch.
+ // Basically the row array is shifted up by 1, and the length is treated
+ // as height - 2.
+ // For each row, shift the array to the left by 1, and treat the length as
+ // width - 2.
+ image->width -= 2;
+ image->height -= 2;
+ memmove(image->rows.get(), image->rows.get() + 1,
+ image->height * sizeof(uint8_t**));
+ for (int32_t h = 0; h < image->height; h++) {
+ memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
+ }
+
+ if (context->verbose()) {
+ context->getDiagnostics()->note(DiagMessage(pathData.source)
+ << "9-patch: " << *ninePatch);
+ }
}
- return true;
+
+ // Write the crunched PNG.
+ if (!writePng(context, image.get(), ninePatch.get(), &crunchedPngBufferOut,
+ {})) {
+ return false;
+ }
+
+ if (ninePatch != nullptr ||
+ crunchedPngBufferOut.ByteCount() <= pngChunkFilter.ByteCount()) {
+ // No matter what, we must use the re-encoded PNG, even if it is larger.
+ // 9-patch images must be re-encoded since their borders are stripped.
+ buffer.appendBuffer(std::move(crunchedPngBuffer));
+ } else {
+ // The re-encoded PNG is larger than the original, and there is
+ // no mandatory transformation. Use the original.
+ if (context->verbose()) {
+ context->getDiagnostics()->note(
+ DiagMessage(pathData.source)
+ << "original PNG is smaller than crunched PNG"
+ << ", using original");
+ }
+
+ PngChunkFilter pngChunkFilterAgain(content);
+ BigBuffer filteredPngBuffer(4096);
+ BigBufferOutputStream filteredPngBufferOut(&filteredPngBuffer);
+ io::copy(&filteredPngBufferOut, &pngChunkFilterAgain);
+ buffer.appendBuffer(std::move(filteredPngBuffer));
+ }
+
+ if (context->verbose()) {
+ // For debugging only, use the legacy PNG cruncher and compare the
+ // resulting file sizes.
+ // This will help catch exotic cases where the new code may generate
+ // larger PNGs.
+ std::stringstream legacyStream(content);
+ BigBuffer legacyBuffer(4096);
+ Png png(context->getDiagnostics());
+ if (!png.process(pathData.source, &legacyStream, &legacyBuffer, {})) {
+ return false;
+ }
+
+ context->getDiagnostics()->note(DiagMessage(pathData.source)
+ << "legacy=" << legacyBuffer.size()
+ << " new=" << buffer.size());
+ }
+ }
+
+ if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
+ context->getDiagnostics())) {
+ return false;
+ }
+ return true;
}
static bool compileFile(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, IArchiveWriter* writer,
- const std::string& outputPath) {
- if (context->verbose()) {
- context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling file");
- }
+ const ResourcePathData& pathData,
+ IArchiveWriter* writer, const std::string& outputPath) {
+ if (context->verbose()) {
+ context->getDiagnostics()->note(DiagMessage(pathData.source)
+ << "compiling file");
+ }
- BigBuffer buffer(256);
- ResourceFile resFile;
- resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
- resFile.config = pathData.config;
- resFile.source = pathData.source;
+ BigBuffer buffer(256);
+ ResourceFile resFile;
+ resFile.name =
+ ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
+ resFile.config = pathData.config;
+ resFile.source = pathData.source;
- std::string errorStr;
- Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
- if (!f) {
- context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
- return false;
- }
+ std::string errorStr;
+ Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
+ if (!f) {
+ context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
+ return false;
+ }
- if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer,
- context->getDiagnostics())) {
- return false;
- }
- return true;
+ if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer,
+ context->getDiagnostics())) {
+ return false;
+ }
+ return true;
}
class CompileContext : public IAaptContext {
-public:
- void setVerbose(bool val) {
- mVerbose = val;
- }
+ public:
+ void setVerbose(bool val) { mVerbose = val; }
- bool verbose() override {
- return mVerbose;
- }
+ bool verbose() override { return mVerbose; }
- IDiagnostics* getDiagnostics() override {
- return &mDiagnostics;
- }
+ IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
- NameMangler* getNameMangler() override {
- abort();
- return nullptr;
- }
+ NameMangler* getNameMangler() override {
+ abort();
+ return nullptr;
+ }
- const std::string& getCompilationPackage() override {
- static std::string empty;
- return empty;
- }
+ const std::string& getCompilationPackage() override {
+ static std::string empty;
+ return empty;
+ }
- uint8_t getPackageId() override {
- return 0x0;
- }
+ uint8_t getPackageId() override { return 0x0; }
- SymbolTable* getExternalSymbols() override {
- abort();
- return nullptr;
- }
+ SymbolTable* getExternalSymbols() override {
+ abort();
+ return nullptr;
+ }
- int getMinSdkVersion() override {
- return 0;
- }
+ int getMinSdkVersion() override { return 0; }
-private:
- StdErrDiagnostics mDiagnostics;
- bool mVerbose = false;
-
+ private:
+ StdErrDiagnostics mDiagnostics;
+ bool mVerbose = false;
};
/**
- * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
+ * Entry point for compilation phase. Parses arguments and dispatches to the
+ * correct steps.
*/
int compile(const std::vector<StringPiece>& args) {
- CompileContext context;
- CompileOptions options;
+ CompileContext context;
+ CompileOptions options;
- bool verbose = false;
- Flags flags = Flags()
- .requiredFlag("-o", "Output path", &options.outputPath)
- .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
- .optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales "
- "(en-XA and ar-XB)", &options.pseudolocalize)
- .optionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
- &options.legacyMode)
- .optionalSwitch("-v", "Enables verbose logging", &verbose);
- if (!flags.parse("aapt2 compile", args, &std::cerr)) {
- return 1;
+ bool verbose = false;
+ Flags flags =
+ Flags()
+ .requiredFlag("-o", "Output path", &options.outputPath)
+ .optionalFlag("--dir", "Directory to scan for resources",
+ &options.resDir)
+ .optionalSwitch("--pseudo-localize",
+ "Generate resources for pseudo-locales "
+ "(en-XA and ar-XB)",
+ &options.pseudolocalize)
+ .optionalSwitch(
+ "--legacy",
+ "Treat errors that used to be valid in AAPT as warnings",
+ &options.legacyMode)
+ .optionalSwitch("-v", "Enables verbose logging", &verbose);
+ if (!flags.parse("aapt2 compile", args, &std::cerr)) {
+ return 1;
+ }
+
+ context.setVerbose(verbose);
+
+ std::unique_ptr<IArchiveWriter> archiveWriter;
+
+ std::vector<ResourcePathData> inputData;
+ if (options.resDir) {
+ if (!flags.getArgs().empty()) {
+ // Can't have both files and a resource directory.
+ context.getDiagnostics()->error(DiagMessage()
+ << "files given but --dir specified");
+ flags.usage("aapt2 compile", &std::cerr);
+ return 1;
}
- context.setVerbose(verbose);
+ if (!loadInputFilesFromDir(&context, options, &inputData)) {
+ return 1;
+ }
- std::unique_ptr<IArchiveWriter> archiveWriter;
+ archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(),
+ options.outputPath);
- std::vector<ResourcePathData> inputData;
- if (options.resDir) {
- if (!flags.getArgs().empty()) {
- // Can't have both files and a resource directory.
- context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified");
- flags.usage("aapt2 compile", &std::cerr);
- return 1;
- }
+ } else {
+ inputData.reserve(flags.getArgs().size());
- if (!loadInputFilesFromDir(&context, options, &inputData)) {
- return 1;
- }
+ // Collect data from the path for each input file.
+ for (const std::string& arg : flags.getArgs()) {
+ std::string errorStr;
+ if (Maybe<ResourcePathData> pathData =
+ extractResourcePathData(arg, &errorStr)) {
+ inputData.push_back(std::move(pathData.value()));
+ } else {
+ context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg
+ << ")");
+ return 1;
+ }
+ }
- archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
+ archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(),
+ options.outputPath);
+ }
+
+ if (!archiveWriter) {
+ return false;
+ }
+
+ bool error = false;
+ for (ResourcePathData& pathData : inputData) {
+ if (options.verbose) {
+ context.getDiagnostics()->note(DiagMessage(pathData.source)
+ << "processing");
+ }
+
+ if (pathData.resourceDir == "values") {
+ // Overwrite the extension.
+ pathData.extension = "arsc";
+
+ const std::string outputFilename = buildIntermediateFilename(pathData);
+ if (!compileTable(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
+ }
} else {
- inputData.reserve(flags.getArgs().size());
-
- // Collect data from the path for each input file.
- for (const std::string& arg : flags.getArgs()) {
- std::string errorStr;
- if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
- inputData.push_back(std::move(pathData.value()));
- } else {
- context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
- return 1;
+ const std::string outputFilename = buildIntermediateFilename(pathData);
+ if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
+ if (*type != ResourceType::kRaw) {
+ if (pathData.extension == "xml") {
+ if (!compileXml(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
}
- }
-
- archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath);
- }
-
- if (!archiveWriter) {
- return false;
- }
-
- bool error = false;
- for (ResourcePathData& pathData : inputData) {
- if (options.verbose) {
- context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
- }
-
- if (pathData.resourceDir == "values") {
- // Overwrite the extension.
- pathData.extension = "arsc";
-
- const std::string outputFilename = buildIntermediateFilename(pathData);
- if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) {
- error = true;
+ } else if (pathData.extension == "png" ||
+ pathData.extension == "9.png") {
+ if (!compilePng(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
}
-
+ } else {
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
+ }
+ }
} else {
- const std::string outputFilename = buildIntermediateFilename(pathData);
- if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
- if (*type != ResourceType::kRaw) {
- if (pathData.extension == "xml") {
- if (!compileXml(&context, options, pathData, archiveWriter.get(),
- outputFilename)) {
- error = true;
- }
- } else if (pathData.extension == "png" || pathData.extension == "9.png") {
- if (!compilePng(&context, options, pathData, archiveWriter.get(),
- outputFilename)) {
- error = true;
- }
- } else {
- if (!compileFile(&context, options, pathData, archiveWriter.get(),
- outputFilename)) {
- error = true;
- }
- }
- } else {
- if (!compileFile(&context, options, pathData, archiveWriter.get(),
- outputFilename)) {
- error = true;
- }
- }
- } else {
- context.getDiagnostics()->error(
- DiagMessage() << "invalid file path '" << pathData.source << "'");
- error = true;
- }
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
+ error = true;
+ }
}
+ } else {
+ context.getDiagnostics()->error(
+ DiagMessage() << "invalid file path '" << pathData.source << "'");
+ error = true;
+ }
}
+ }
- if (error) {
- return 1;
- }
- return 0;
+ if (error) {
+ return 1;
+ }
+ return 0;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index 4a3f1e1..73eb066 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include "ResourceTable.h"
#include "compile/IdAssigner.h"
+#include "ResourceTable.h"
#include "process/IResourceTableConsumer.h"
#include "util/Util.h"
@@ -25,178 +25,192 @@
namespace aapt {
/**
- * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and ResourceEntry,
+ * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and
+ * ResourceEntry,
* as long as there is no existing ID or the ID is the same.
*/
-static bool assignId(IDiagnostics* diag, const ResourceId& id, const ResourceName& name,
- ResourceTablePackage* pkg, ResourceTableType* type, ResourceEntry* entry) {
- if (pkg->id.value() == id.packageId()) {
- if (!type->id || type->id.value() == id.typeId()) {
- type->id = id.typeId();
+static bool assignId(IDiagnostics* diag, const ResourceId& id,
+ const ResourceName& name, ResourceTablePackage* pkg,
+ ResourceTableType* type, ResourceEntry* entry) {
+ if (pkg->id.value() == id.packageId()) {
+ if (!type->id || type->id.value() == id.typeId()) {
+ type->id = id.typeId();
- if (!entry->id || entry->id.value() == id.entryId()) {
- entry->id = id.entryId();
- return true;
- }
- }
+ if (!entry->id || entry->id.value() == id.entryId()) {
+ entry->id = id.entryId();
+ return true;
+ }
}
+ }
- const ResourceId existingId(pkg->id.value(),
- type->id ? type->id.value() : 0,
- entry->id ? entry->id.value() : 0);
- diag->error(DiagMessage() << "can't assign ID " << id
- << " to resource " << name
- << " with conflicting ID " << existingId);
- return false;
+ const ResourceId existingId(pkg->id.value(), type->id ? type->id.value() : 0,
+ entry->id ? entry->id.value() : 0);
+ diag->error(DiagMessage() << "can't assign ID " << id << " to resource "
+ << name << " with conflicting ID " << existingId);
+ return false;
}
bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
- std::map<ResourceId, ResourceName> assignedIds;
+ std::map<ResourceId, ResourceName> assignedIds;
- for (auto& package : table->packages) {
- assert(package->id && "packages must have manually assigned IDs");
+ for (auto& package : table->packages) {
+ assert(package->id && "packages must have manually assigned IDs");
- for (auto& type : package->types) {
- for (auto& entry : type->entries) {
- const ResourceName name(package->name, type->type, entry->name);
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ const ResourceName name(package->name, type->type, entry->name);
- if (mAssignedIdMap) {
- // Assign the pre-assigned stable ID meant for this resource.
- const auto iter = mAssignedIdMap->find(name);
- if (iter != mAssignedIdMap->end()) {
- const ResourceId assignedId = iter->second;
- const bool result = assignId(context->getDiagnostics(), assignedId, name,
- package.get(), type.get(), entry.get());
- if (!result) {
- return false;
- }
- }
- }
-
- if (package->id && type->id && entry->id) {
- // If the ID is set for this resource, then reserve it.
- ResourceId resourceId(package->id.value(), type->id.value(), entry->id.value());
- auto result = assignedIds.insert({ resourceId, name });
- const ResourceName& existingName = result.first->second;
- if (!result.second) {
- context->getDiagnostics()->error(DiagMessage() << "resource " << name
- << " has same ID "
- << resourceId
- << " as " << existingName);
- return false;
- }
- }
+ if (mAssignedIdMap) {
+ // Assign the pre-assigned stable ID meant for this resource.
+ const auto iter = mAssignedIdMap->find(name);
+ if (iter != mAssignedIdMap->end()) {
+ const ResourceId assignedId = iter->second;
+ const bool result =
+ assignId(context->getDiagnostics(), assignedId, name,
+ package.get(), type.get(), entry.get());
+ if (!result) {
+ return false;
}
+ }
}
- }
- if (mAssignedIdMap) {
- // Reserve all the IDs mentioned in the stable ID map. That way we won't assign
- // IDs that were listed in the map if they don't exist in the table.
- for (const auto& stableIdEntry : *mAssignedIdMap) {
- const ResourceName& preAssignedName = stableIdEntry.first;
- const ResourceId& preAssignedId = stableIdEntry.second;
- auto result = assignedIds.insert({ preAssignedId, preAssignedName });
- const ResourceName& existingName = result.first->second;
- if (!result.second && existingName != preAssignedName) {
- context->getDiagnostics()->error(DiagMessage() << "stable ID " << preAssignedId
- << " for resource " << preAssignedName
- << " is already taken by resource "
- << existingName);
- return false;
- }
+ if (package->id && type->id && entry->id) {
+ // If the ID is set for this resource, then reserve it.
+ ResourceId resourceId(package->id.value(), type->id.value(),
+ entry->id.value());
+ auto result = assignedIds.insert({resourceId, name});
+ const ResourceName& existingName = result.first->second;
+ if (!result.second) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "resource " << name << " has same ID "
+ << resourceId << " as " << existingName);
+ return false;
+ }
}
+ }
}
+ }
- // Assign any resources without IDs the next available ID. Gaps will be filled if possible,
- // unless those IDs have been reserved.
+ if (mAssignedIdMap) {
+ // Reserve all the IDs mentioned in the stable ID map. That way we won't
+ // assign
+ // IDs that were listed in the map if they don't exist in the table.
+ for (const auto& stableIdEntry : *mAssignedIdMap) {
+ const ResourceName& preAssignedName = stableIdEntry.first;
+ const ResourceId& preAssignedId = stableIdEntry.second;
+ auto result = assignedIds.insert({preAssignedId, preAssignedName});
+ const ResourceName& existingName = result.first->second;
+ if (!result.second && existingName != preAssignedName) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "stable ID " << preAssignedId << " for resource "
+ << preAssignedName << " is already taken by resource "
+ << existingName);
+ return false;
+ }
+ }
+ }
- const auto assignedIdsIterEnd = assignedIds.end();
- for (auto& package : table->packages) {
- assert(package->id && "packages must have manually assigned IDs");
+ // Assign any resources without IDs the next available ID. Gaps will be filled
+ // if possible,
+ // unless those IDs have been reserved.
- // Build a half filled ResourceId object, which will be used to find the closest matching
- // reserved ID in the assignedId map. From that point the next available type ID can be
- // found.
- ResourceId resourceId(package->id.value(), 0, 0);
- uint8_t nextExpectedTypeId = 1;
+ const auto assignedIdsIterEnd = assignedIds.end();
+ for (auto& package : table->packages) {
+ assert(package->id && "packages must have manually assigned IDs");
- // Find the closest matching ResourceId that is <= the one with only the package set.
- auto nextTypeIter = assignedIds.lower_bound(resourceId);
- for (auto& type : package->types) {
- if (!type->id) {
- // We need to assign a type ID. Iterate over the reserved IDs until we find
- // some type ID that is a distance of 2 greater than the last one we've seen.
- // That means there is an available type ID between these reserved IDs.
- while (nextTypeIter != assignedIdsIterEnd) {
- if (nextTypeIter->first.packageId() != package->id.value()) {
- break;
- }
+ // Build a half filled ResourceId object, which will be used to find the
+ // closest matching
+ // reserved ID in the assignedId map. From that point the next available
+ // type ID can be
+ // found.
+ ResourceId resourceId(package->id.value(), 0, 0);
+ uint8_t nextExpectedTypeId = 1;
- const uint8_t typeId = nextTypeIter->first.typeId();
- if (typeId > nextExpectedTypeId) {
- // There is a gap in the type IDs, so use the missing one.
- type->id = nextExpectedTypeId++;
- break;
- }
+ // Find the closest matching ResourceId that is <= the one with only the
+ // package set.
+ auto nextTypeIter = assignedIds.lower_bound(resourceId);
+ for (auto& type : package->types) {
+ if (!type->id) {
+ // We need to assign a type ID. Iterate over the reserved IDs until we
+ // find
+ // some type ID that is a distance of 2 greater than the last one we've
+ // seen.
+ // That means there is an available type ID between these reserved IDs.
+ while (nextTypeIter != assignedIdsIterEnd) {
+ if (nextTypeIter->first.packageId() != package->id.value()) {
+ break;
+ }
- // Set our expectation to be the next type ID after the reserved one we
- // just saw.
- nextExpectedTypeId = typeId + 1;
+ const uint8_t typeId = nextTypeIter->first.typeId();
+ if (typeId > nextExpectedTypeId) {
+ // There is a gap in the type IDs, so use the missing one.
+ type->id = nextExpectedTypeId++;
+ break;
+ }
- // Move to the next reserved ID.
- ++nextTypeIter;
- }
+ // Set our expectation to be the next type ID after the reserved one
+ // we
+ // just saw.
+ nextExpectedTypeId = typeId + 1;
- if (!type->id) {
- // We must have hit the end of the reserved IDs and not found a gap.
- // That means the next ID is available.
- type->id = nextExpectedTypeId++;
- }
- }
-
- resourceId = ResourceId(package->id.value(), type->id.value(), 0);
- uint16_t nextExpectedEntryId = 0;
-
- // Find the closest matching ResourceId that is <= the one with only the package
- // and type set.
- auto nextEntryIter = assignedIds.lower_bound(resourceId);
- for (auto& entry : type->entries) {
- if (!entry->id) {
- // We need to assign an entry ID. Iterate over the reserved IDs until we find
- // some entry ID that is a distance of 2 greater than the last one we've seen.
- // That means there is an available entry ID between these reserved IDs.
- while (nextEntryIter != assignedIdsIterEnd) {
- if (nextEntryIter->first.packageId() != package->id.value() ||
- nextEntryIter->first.typeId() != type->id.value()) {
- break;
- }
-
- const uint16_t entryId = nextEntryIter->first.entryId();
- if (entryId > nextExpectedEntryId) {
- // There is a gap in the entry IDs, so use the missing one.
- entry->id = nextExpectedEntryId++;
- break;
- }
-
- // Set our expectation to be the next type ID after the reserved one we
- // just saw.
- nextExpectedEntryId = entryId + 1;
-
- // Move to the next reserved entry ID.
- ++nextEntryIter;
- }
-
- if (!entry->id) {
- // We must have hit the end of the reserved IDs and not found a gap.
- // That means the next ID is available.
- entry->id = nextExpectedEntryId++;
- }
- }
- }
+ // Move to the next reserved ID.
+ ++nextTypeIter;
}
+
+ if (!type->id) {
+ // We must have hit the end of the reserved IDs and not found a gap.
+ // That means the next ID is available.
+ type->id = nextExpectedTypeId++;
+ }
+ }
+
+ resourceId = ResourceId(package->id.value(), type->id.value(), 0);
+ uint16_t nextExpectedEntryId = 0;
+
+ // Find the closest matching ResourceId that is <= the one with only the
+ // package
+ // and type set.
+ auto nextEntryIter = assignedIds.lower_bound(resourceId);
+ for (auto& entry : type->entries) {
+ if (!entry->id) {
+ // We need to assign an entry ID. Iterate over the reserved IDs until
+ // we find
+ // some entry ID that is a distance of 2 greater than the last one
+ // we've seen.
+ // That means there is an available entry ID between these reserved
+ // IDs.
+ while (nextEntryIter != assignedIdsIterEnd) {
+ if (nextEntryIter->first.packageId() != package->id.value() ||
+ nextEntryIter->first.typeId() != type->id.value()) {
+ break;
+ }
+
+ const uint16_t entryId = nextEntryIter->first.entryId();
+ if (entryId > nextExpectedEntryId) {
+ // There is a gap in the entry IDs, so use the missing one.
+ entry->id = nextExpectedEntryId++;
+ break;
+ }
+
+ // Set our expectation to be the next type ID after the reserved one
+ // we
+ // just saw.
+ nextExpectedEntryId = entryId + 1;
+
+ // Move to the next reserved entry ID.
+ ++nextEntryIter;
+ }
+
+ if (!entry->id) {
+ // We must have hit the end of the reserved IDs and not found a gap.
+ // That means the next ID is available.
+ entry->id = nextExpectedEntryId++;
+ }
+ }
+ }
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h
index 06cd5e3..d399064 100644
--- a/tools/aapt2/compile/IdAssigner.h
+++ b/tools/aapt2/compile/IdAssigner.h
@@ -26,22 +26,22 @@
namespace aapt {
/**
- * Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps
+ * Assigns IDs to each resource in the table, respecting existing IDs and
+ * filling in gaps
* in between fixed ID assignments.
*/
class IdAssigner : public IResourceTableConsumer {
-public:
- IdAssigner() = default;
- explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map) :
- mAssignedIdMap(map) {
- }
+ public:
+ IdAssigner() = default;
+ explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map)
+ : mAssignedIdMap(map) {}
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ bool consume(IAaptContext* context, ResourceTable* table) override;
-private:
- const std::unordered_map<ResourceName, ResourceId>* mAssignedIdMap = nullptr;
+ private:
+ const std::unordered_map<ResourceName, ResourceId>* mAssignedIdMap = nullptr;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_IDASSIGNER_H */
diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp
index d21fcba..ff7bf5c 100644
--- a/tools/aapt2/compile/IdAssigner_test.cpp
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -22,154 +22,163 @@
::testing::AssertionResult verifyIds(ResourceTable* table);
TEST(IdAssignerTest, AssignIds) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addSimple("android:attr/foo")
- .addSimple("android:attr/bar")
- .addSimple("android:id/foo")
- .setPackageId("android", 0x01)
- .build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addSimple("android:attr/foo")
+ .addSimple("android:attr/bar")
+ .addSimple("android:id/foo")
+ .setPackageId("android", 0x01)
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- IdAssigner assigner;
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ IdAssigner assigner;
- ASSERT_TRUE(assigner.consume(context.get(), table.get()));
- ASSERT_TRUE(verifyIds(table.get()));
+ ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+ ASSERT_TRUE(verifyIds(table.get()));
}
TEST(IdAssignerTest, AssignIdsWithReservedIds) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addSimple("android:id/foo", ResourceId(0x01010000))
- .addSimple("android:dimen/two")
- .addSimple("android:integer/three")
- .addSimple("android:string/five")
- .addSimple("android:attr/fun", ResourceId(0x01040000))
- .addSimple("android:attr/foo", ResourceId(0x01040006))
- .addSimple("android:attr/bar")
- .addSimple("android:attr/baz")
- .addSimple("app:id/biz")
- .setPackageId("android", 0x01)
- .setPackageId("app", 0x7f)
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .addSimple("android:id/foo", ResourceId(0x01010000))
+ .addSimple("android:dimen/two")
+ .addSimple("android:integer/three")
+ .addSimple("android:string/five")
+ .addSimple("android:attr/fun", ResourceId(0x01040000))
+ .addSimple("android:attr/foo", ResourceId(0x01040006))
+ .addSimple("android:attr/bar")
+ .addSimple("android:attr/baz")
+ .addSimple("app:id/biz")
+ .setPackageId("android", 0x01)
+ .setPackageId("app", 0x7f)
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- IdAssigner assigner;
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ IdAssigner assigner;
- ASSERT_TRUE(assigner.consume(context.get(), table.get()));
- ASSERT_TRUE(verifyIds(table.get()));
+ ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+ ASSERT_TRUE(verifyIds(table.get()));
- Maybe<ResourceTable::SearchResult> maybeResult;
+ Maybe<ResourceTable::SearchResult> maybeResult;
- // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX.
+ // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX.
- maybeResult = table->findResource(test::parseNameOrDie("android:dimen/two"));
- AAPT_ASSERT_TRUE(maybeResult);
- EXPECT_EQ(make_value<uint8_t>(2), maybeResult.value().type->id);
+ maybeResult = table->findResource(test::parseNameOrDie("android:dimen/two"));
+ AAPT_ASSERT_TRUE(maybeResult);
+ EXPECT_EQ(make_value<uint8_t>(2), maybeResult.value().type->id);
- maybeResult = table->findResource(test::parseNameOrDie("android:integer/three"));
- AAPT_ASSERT_TRUE(maybeResult);
- EXPECT_EQ(make_value<uint8_t>(3), maybeResult.value().type->id);
+ maybeResult =
+ table->findResource(test::parseNameOrDie("android:integer/three"));
+ AAPT_ASSERT_TRUE(maybeResult);
+ EXPECT_EQ(make_value<uint8_t>(3), maybeResult.value().type->id);
- // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX IDs.
+ // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX
+ // IDs.
- maybeResult = table->findResource(test::parseNameOrDie("android:string/five"));
- AAPT_ASSERT_TRUE(maybeResult);
- EXPECT_EQ(make_value<uint8_t>(5), maybeResult.value().type->id);
+ maybeResult =
+ table->findResource(test::parseNameOrDie("android:string/five"));
+ AAPT_ASSERT_TRUE(maybeResult);
+ EXPECT_EQ(make_value<uint8_t>(5), maybeResult.value().type->id);
- // Expect to fill in the gaps between 0x01040000 and 0x01040006.
+ // Expect to fill in the gaps between 0x01040000 and 0x01040006.
- maybeResult = table->findResource(test::parseNameOrDie("android:attr/bar"));
- AAPT_ASSERT_TRUE(maybeResult);
- EXPECT_EQ(make_value<uint16_t>(1), maybeResult.value().entry->id);
+ maybeResult = table->findResource(test::parseNameOrDie("android:attr/bar"));
+ AAPT_ASSERT_TRUE(maybeResult);
+ EXPECT_EQ(make_value<uint16_t>(1), maybeResult.value().entry->id);
- maybeResult = table->findResource(test::parseNameOrDie("android:attr/baz"));
- AAPT_ASSERT_TRUE(maybeResult);
- EXPECT_EQ(make_value<uint16_t>(2), maybeResult.value().entry->id);
+ maybeResult = table->findResource(test::parseNameOrDie("android:attr/baz"));
+ AAPT_ASSERT_TRUE(maybeResult);
+ EXPECT_EQ(make_value<uint16_t>(2), maybeResult.value().entry->id);
}
TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addSimple("android:attr/foo", ResourceId(0x01040006))
- .addSimple("android:attr/bar", ResourceId(0x01040006))
- .setPackageId("android", 0x01)
- .setPackageId("app", 0x7f)
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .addSimple("android:attr/foo", ResourceId(0x01040006))
+ .addSimple("android:attr/bar", ResourceId(0x01040006))
+ .setPackageId("android", 0x01)
+ .setPackageId("app", 0x7f)
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- IdAssigner assigner;
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ IdAssigner assigner;
- ASSERT_FALSE(assigner.consume(context.get(), table.get()));
+ ASSERT_FALSE(assigner.consume(context.get(), table.get()));
}
TEST(IdAssignerTest, AssignIdsWithIdMap) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addSimple("android:attr/foo")
- .addSimple("android:attr/bar")
- .setPackageId("android", 0x01)
- .build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .addSimple("android:attr/foo")
+ .addSimple("android:attr/bar")
+ .setPackageId("android", 0x01)
+ .build();
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unordered_map<ResourceName, ResourceId> idMap = {
- { test::parseNameOrDie("android:attr/foo"), ResourceId(0x01010002) } };
- IdAssigner assigner(&idMap);
- ASSERT_TRUE(assigner.consume(context.get(), table.get()));
- ASSERT_TRUE(verifyIds(table.get()));
- Maybe<ResourceTable::SearchResult> result = table->findResource(
- test::parseNameOrDie("android:attr/foo"));
- AAPT_ASSERT_TRUE(result);
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unordered_map<ResourceName, ResourceId> idMap = {
+ {test::parseNameOrDie("android:attr/foo"), ResourceId(0x01010002)}};
+ IdAssigner assigner(&idMap);
+ ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+ ASSERT_TRUE(verifyIds(table.get()));
+ Maybe<ResourceTable::SearchResult> result =
+ table->findResource(test::parseNameOrDie("android:attr/foo"));
+ AAPT_ASSERT_TRUE(result);
- const ResourceTable::SearchResult& searchResult = result.value();
- EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.package->id);
- EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.type->id);
- EXPECT_EQ(make_value<uint16_t>(0x0002), searchResult.entry->id);
+ const ResourceTable::SearchResult& searchResult = result.value();
+ EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.package->id);
+ EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.type->id);
+ EXPECT_EQ(make_value<uint16_t>(0x0002), searchResult.entry->id);
}
::testing::AssertionResult verifyIds(ResourceTable* table) {
- std::set<uint8_t> packageIds;
- for (auto& package : table->packages) {
- if (!package->id) {
- return ::testing::AssertionFailure() << "package " << package->name << " has no ID";
- }
-
- if (!packageIds.insert(package->id.value()).second) {
- return ::testing::AssertionFailure() << "package " << package->name
- << " has non-unique ID " << std::hex << (int) package->id.value() << std::dec;
- }
+ std::set<uint8_t> packageIds;
+ for (auto& package : table->packages) {
+ if (!package->id) {
+ return ::testing::AssertionFailure() << "package " << package->name
+ << " has no ID";
}
- for (auto& package : table->packages) {
- std::set<uint8_t> typeIds;
- for (auto& type : package->types) {
- if (!type->id) {
- return ::testing::AssertionFailure() << "type " << type->type << " of package "
- << package->name << " has no ID";
- }
-
- if (!typeIds.insert(type->id.value()).second) {
- return ::testing::AssertionFailure() << "type " << type->type
- << " of package " << package->name << " has non-unique ID "
- << std::hex << (int) type->id.value() << std::dec;
- }
- }
-
-
- for (auto& type : package->types) {
- std::set<uint16_t> entryIds;
- for (auto& entry : type->entries) {
- if (!entry->id) {
- return ::testing::AssertionFailure() << "entry " << entry->name << " of type "
- << type->type << " of package " << package->name << " has no ID";
- }
-
- if (!entryIds.insert(entry->id.value()).second) {
- return ::testing::AssertionFailure() << "entry " << entry->name
- << " of type " << type->type << " of package " << package->name
- << " has non-unique ID "
- << std::hex << (int) entry->id.value() << std::dec;
- }
- }
- }
+ if (!packageIds.insert(package->id.value()).second) {
+ return ::testing::AssertionFailure()
+ << "package " << package->name << " has non-unique ID " << std::hex
+ << (int)package->id.value() << std::dec;
}
- return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
+ }
+
+ for (auto& package : table->packages) {
+ std::set<uint8_t> typeIds;
+ for (auto& type : package->types) {
+ if (!type->id) {
+ return ::testing::AssertionFailure() << "type " << type->type
+ << " of package " << package->name
+ << " has no ID";
+ }
+
+ if (!typeIds.insert(type->id.value()).second) {
+ return ::testing::AssertionFailure()
+ << "type " << type->type << " of package " << package->name
+ << " has non-unique ID " << std::hex << (int)type->id.value()
+ << std::dec;
+ }
+ }
+
+ for (auto& type : package->types) {
+ std::set<uint16_t> entryIds;
+ for (auto& entry : type->entries) {
+ if (!entry->id) {
+ return ::testing::AssertionFailure()
+ << "entry " << entry->name << " of type " << type->type
+ << " of package " << package->name << " has no ID";
+ }
+
+ if (!entryIds.insert(entry->id.value()).second) {
+ return ::testing::AssertionFailure()
+ << "entry " << entry->name << " of type " << type->type
+ << " of package " << package->name << " has non-unique ID "
+ << std::hex << (int)entry->id.value() << std::dec;
+ }
+ }
+ }
+ }
+ return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Image.h b/tools/aapt2/compile/Image.h
index fda6a3a..4cf2ea7 100644
--- a/tools/aapt2/compile/Image.h
+++ b/tools/aapt2/compile/Image.h
@@ -29,173 +29,180 @@
* An in-memory image, loaded from disk, with pixels in RGBA_8888 format.
*/
class Image {
-public:
- explicit Image() = default;
+ public:
+ explicit Image() = default;
- /**
- * A `height` sized array of pointers, where each element points to a
- * `width` sized row of RGBA_8888 pixels.
- */
- std::unique_ptr<uint8_t*[]> rows;
+ /**
+ * A `height` sized array of pointers, where each element points to a
+ * `width` sized row of RGBA_8888 pixels.
+ */
+ std::unique_ptr<uint8_t* []> rows;
- /**
- * The width of the image in RGBA_8888 pixels. This is int32_t because of 9-patch data
- * format limitations.
- */
- int32_t width = 0;
+ /**
+ * The width of the image in RGBA_8888 pixels. This is int32_t because of
+ * 9-patch data
+ * format limitations.
+ */
+ int32_t width = 0;
- /**
- * The height of the image in RGBA_8888 pixels. This is int32_t because of 9-patch data
- * format limitations.
- */
- int32_t height = 0;
+ /**
+ * The height of the image in RGBA_8888 pixels. This is int32_t because of
+ * 9-patch data
+ * format limitations.
+ */
+ int32_t height = 0;
- /**
- * Buffer to the raw image data stored sequentially.
- * Use `rows` to access the data on a row-by-row basis.
- */
- std::unique_ptr<uint8_t[]> data;
+ /**
+ * Buffer to the raw image data stored sequentially.
+ * Use `rows` to access the data on a row-by-row basis.
+ */
+ std::unique_ptr<uint8_t[]> data;
-private:
- DISALLOW_COPY_AND_ASSIGN(Image);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Image);
};
/**
- * A range of pixel values, starting at 'start' and ending before 'end' exclusive. Or rather [a, b).
+ * A range of pixel values, starting at 'start' and ending before 'end'
+ * exclusive. Or rather [a, b).
*/
struct Range {
- int32_t start = 0;
- int32_t end = 0;
+ int32_t start = 0;
+ int32_t end = 0;
- explicit Range() = default;
- inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {
- }
+ explicit Range() = default;
+ inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {}
};
inline bool operator==(const Range& left, const Range& right) {
- return left.start == right.start && left.end == right.end;
+ return left.start == right.start && left.end == right.end;
}
/**
- * Inset lengths from all edges of a rectangle. `left` and `top` are measured from the left and top
- * edges, while `right` and `bottom` are measured from the right and bottom edges, respectively.
+ * Inset lengths from all edges of a rectangle. `left` and `top` are measured
+ * from the left and top
+ * edges, while `right` and `bottom` are measured from the right and bottom
+ * edges, respectively.
*/
struct Bounds {
- int32_t left = 0;
- int32_t top = 0;
- int32_t right = 0;
- int32_t bottom = 0;
+ int32_t left = 0;
+ int32_t top = 0;
+ int32_t right = 0;
+ int32_t bottom = 0;
- explicit Bounds() = default;
- inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b) :
- left(l), top(t), right(r), bottom(b) {
- }
+ explicit Bounds() = default;
+ inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b)
+ : left(l), top(t), right(r), bottom(b) {}
- bool nonZero() const;
+ bool nonZero() const;
};
inline bool Bounds::nonZero() const {
- return left != 0 || top != 0 || right != 0 || bottom != 0;
+ return left != 0 || top != 0 || right != 0 || bottom != 0;
}
inline bool operator==(const Bounds& left, const Bounds& right) {
- return left.left == right.left && left.top == right.top &&
- left.right == right.right && left.bottom == right.bottom;
+ return left.left == right.left && left.top == right.top &&
+ left.right == right.right && left.bottom == right.bottom;
}
/**
- * Contains 9-patch data from a source image. All measurements exclude the 1px border of the
+ * Contains 9-patch data from a source image. All measurements exclude the 1px
+ * border of the
* source 9-patch image.
*/
class NinePatch {
-public:
- static std::unique_ptr<NinePatch> create(uint8_t** rows,
- const int32_t width, const int32_t height,
- std::string* errOut);
+ public:
+ static std::unique_ptr<NinePatch> create(uint8_t** rows, const int32_t width,
+ const int32_t height,
+ std::string* errOut);
- /**
- * Packs the RGBA_8888 data pointed to by pixel into a uint32_t
- * with format 0xAARRGGBB (the way 9-patch expects it).
- */
- static uint32_t packRGBA(const uint8_t* pixel);
+ /**
+ * Packs the RGBA_8888 data pointed to by pixel into a uint32_t
+ * with format 0xAARRGGBB (the way 9-patch expects it).
+ */
+ static uint32_t packRGBA(const uint8_t* pixel);
- /**
- * 9-patch content padding/insets. All positions are relative to the 9-patch
- * NOT including the 1px thick source border.
- */
- Bounds padding;
+ /**
+ * 9-patch content padding/insets. All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ Bounds padding;
- /**
- * Optical layout bounds/insets. This overrides the padding for
- * layout purposes. All positions are relative to the 9-patch
- * NOT including the 1px thick source border.
- * See https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
- */
- Bounds layoutBounds;
+ /**
+ * Optical layout bounds/insets. This overrides the padding for
+ * layout purposes. All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ * See
+ * https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
+ */
+ Bounds layoutBounds;
- /**
- * Outline of the image, calculated based on opacity.
- */
- Bounds outline;
+ /**
+ * Outline of the image, calculated based on opacity.
+ */
+ Bounds outline;
- /**
- * The computed radius of the outline. If non-zero, the outline is a rounded-rect.
- */
- float outlineRadius = 0.0f;
+ /**
+ * The computed radius of the outline. If non-zero, the outline is a
+ * rounded-rect.
+ */
+ float outlineRadius = 0.0f;
- /**
- * The largest alpha value within the outline.
- */
- uint32_t outlineAlpha = 0x000000ffu;
+ /**
+ * The largest alpha value within the outline.
+ */
+ uint32_t outlineAlpha = 0x000000ffu;
- /**
- * Horizontal regions of the image that are stretchable.
- * All positions are relative to the 9-patch
- * NOT including the 1px thick source border.
- */
- std::vector<Range> horizontalStretchRegions;
+ /**
+ * Horizontal regions of the image that are stretchable.
+ * All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ std::vector<Range> horizontalStretchRegions;
- /**
- * Vertical regions of the image that are stretchable.
- * All positions are relative to the 9-patch
- * NOT including the 1px thick source border.
- */
- std::vector<Range> verticalStretchRegions;
+ /**
+ * Vertical regions of the image that are stretchable.
+ * All positions are relative to the 9-patch
+ * NOT including the 1px thick source border.
+ */
+ std::vector<Range> verticalStretchRegions;
- /**
- * The colors within each region, fixed or stretchable.
- * For w*h regions, the color of region (x,y) is addressable
- * via index y*w + x.
- */
- std::vector<uint32_t> regionColors;
+ /**
+ * The colors within each region, fixed or stretchable.
+ * For w*h regions, the color of region (x,y) is addressable
+ * via index y*w + x.
+ */
+ std::vector<uint32_t> regionColors;
- /**
- * Returns serialized data containing the original basic 9-patch meta data.
- * Optical layout bounds and round rect outline data must be serialized
- * separately using serializeOpticalLayoutBounds() and serializeRoundedRectOutline().
- */
- std::unique_ptr<uint8_t[]> serializeBase(size_t* outLen) const;
+ /**
+ * Returns serialized data containing the original basic 9-patch meta data.
+ * Optical layout bounds and round rect outline data must be serialized
+ * separately using serializeOpticalLayoutBounds() and
+ * serializeRoundedRectOutline().
+ */
+ std::unique_ptr<uint8_t[]> serializeBase(size_t* outLen) const;
- /**
- * Serializes the layout bounds.
- */
- std::unique_ptr<uint8_t[]> serializeLayoutBounds(size_t* outLen) const;
+ /**
+ * Serializes the layout bounds.
+ */
+ std::unique_ptr<uint8_t[]> serializeLayoutBounds(size_t* outLen) const;
- /**
- * Serializes the rounded-rect outline.
- */
- std::unique_ptr<uint8_t[]> serializeRoundedRectOutline(size_t* outLen) const;
+ /**
+ * Serializes the rounded-rect outline.
+ */
+ std::unique_ptr<uint8_t[]> serializeRoundedRectOutline(size_t* outLen) const;
-private:
- explicit NinePatch() = default;
+ private:
+ explicit NinePatch() = default;
- DISALLOW_COPY_AND_ASSIGN(NinePatch);
+ DISALLOW_COPY_AND_ASSIGN(NinePatch);
};
::std::ostream& operator<<(::std::ostream& out, const Range& range);
::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds);
::std::ostream& operator<<(::std::ostream& out, const NinePatch& ninePatch);
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_IMAGE_H */
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp
index f965bff..56f72b5 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.cpp
+++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
+#include "compile/InlineXmlFormatParser.h"
#include "Debug.h"
#include "ResourceUtils.h"
-#include "compile/InlineXmlFormatParser.h"
#include "util/Util.h"
#include "xml/XmlDom.h"
#include "xml/XmlUtil.h"
@@ -33,158 +33,172 @@
* XML Visitor that will find all <aapt:attr> elements for extraction.
*/
class Visitor : public xml::PackageAwareVisitor {
-public:
- using xml::PackageAwareVisitor::visit;
+ public:
+ using xml::PackageAwareVisitor::visit;
- struct InlineDeclaration {
- xml::Element* el;
- std::string attrNamespaceUri;
- std::string attrName;
- };
+ struct InlineDeclaration {
+ xml::Element* el;
+ std::string attrNamespaceUri;
+ std::string attrName;
+ };
- explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource) :
- mContext(context), mXmlResource(xmlResource) {
+ explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource)
+ : mContext(context), mXmlResource(xmlResource) {}
+
+ void visit(xml::Element* el) override {
+ if (el->namespaceUri != xml::kSchemaAapt || el->name != "attr") {
+ xml::PackageAwareVisitor::visit(el);
+ return;
}
- void visit(xml::Element* el) override {
- if (el->namespaceUri != xml::kSchemaAapt || el->name != "attr") {
- xml::PackageAwareVisitor::visit(el);
- return;
- }
+ const Source& src = mXmlResource->file.source.withLine(el->lineNumber);
- const Source& src = mXmlResource->file.source.withLine(el->lineNumber);
-
- xml::Attribute* attr = el->findAttribute({}, "name");
- if (!attr) {
- mContext->getDiagnostics()->error(DiagMessage(src) << "missing 'name' attribute");
- mError = true;
- return;
- }
-
- Maybe<Reference> ref = ResourceUtils::parseXmlAttributeName(attr->value);
- if (!ref) {
- mContext->getDiagnostics()->error(DiagMessage(src) << "invalid XML attribute '"
- << attr->value << "'");
- mError = true;
- return;
- }
-
- const ResourceName& name = ref.value().name.value();
-
- // Use an empty string for the compilation package because we don't want to default to
- // the local package if the user specified name="style" or something. This should just
- // be the default namespace.
- Maybe<xml::ExtractedPackage> maybePkg = transformPackageAlias(name.package, {});
- if (!maybePkg) {
- mContext->getDiagnostics()->error(DiagMessage(src) << "invalid namespace prefix '"
- << name.package << "'");
- mError = true;
- return;
- }
-
- const xml::ExtractedPackage& pkg = maybePkg.value();
- const bool privateNamespace = pkg.privateNamespace || ref.value().privateReference;
-
- InlineDeclaration decl;
- decl.el = el;
- decl.attrName = name.entry;
- if (!pkg.package.empty()) {
- decl.attrNamespaceUri = xml::buildPackageNamespace(pkg.package, privateNamespace);
- }
-
- mInlineDeclarations.push_back(std::move(decl));
+ xml::Attribute* attr = el->findAttribute({}, "name");
+ if (!attr) {
+ mContext->getDiagnostics()->error(DiagMessage(src)
+ << "missing 'name' attribute");
+ mError = true;
+ return;
}
- const std::vector<InlineDeclaration>& getInlineDeclarations() const {
- return mInlineDeclarations;
+ Maybe<Reference> ref = ResourceUtils::parseXmlAttributeName(attr->value);
+ if (!ref) {
+ mContext->getDiagnostics()->error(
+ DiagMessage(src) << "invalid XML attribute '" << attr->value << "'");
+ mError = true;
+ return;
}
- bool hasError() const {
- return mError;
+ const ResourceName& name = ref.value().name.value();
+
+ // Use an empty string for the compilation package because we don't want to
+ // default to
+ // the local package if the user specified name="style" or something. This
+ // should just
+ // be the default namespace.
+ Maybe<xml::ExtractedPackage> maybePkg =
+ transformPackageAlias(name.package, {});
+ if (!maybePkg) {
+ mContext->getDiagnostics()->error(DiagMessage(src)
+ << "invalid namespace prefix '"
+ << name.package << "'");
+ mError = true;
+ return;
}
-private:
- DISALLOW_COPY_AND_ASSIGN(Visitor);
+ const xml::ExtractedPackage& pkg = maybePkg.value();
+ const bool privateNamespace =
+ pkg.privateNamespace || ref.value().privateReference;
- IAaptContext* mContext;
- xml::XmlResource* mXmlResource;
- std::vector<InlineDeclaration> mInlineDeclarations;
- bool mError = false;
+ InlineDeclaration decl;
+ decl.el = el;
+ decl.attrName = name.entry;
+ if (!pkg.package.empty()) {
+ decl.attrNamespaceUri =
+ xml::buildPackageNamespace(pkg.package, privateNamespace);
+ }
+
+ mInlineDeclarations.push_back(std::move(decl));
+ }
+
+ const std::vector<InlineDeclaration>& getInlineDeclarations() const {
+ return mInlineDeclarations;
+ }
+
+ bool hasError() const { return mError; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Visitor);
+
+ IAaptContext* mContext;
+ xml::XmlResource* mXmlResource;
+ std::vector<InlineDeclaration> mInlineDeclarations;
+ bool mError = false;
};
-} // namespace
+} // namespace
-bool InlineXmlFormatParser::consume(IAaptContext* context, xml::XmlResource* doc) {
- Visitor visitor(context, doc);
- doc->root->accept(&visitor);
- if (visitor.hasError()) {
+bool InlineXmlFormatParser::consume(IAaptContext* context,
+ xml::XmlResource* doc) {
+ Visitor visitor(context, doc);
+ doc->root->accept(&visitor);
+ if (visitor.hasError()) {
+ return false;
+ }
+
+ size_t nameSuffixCounter = 0;
+ for (const Visitor::InlineDeclaration& decl :
+ visitor.getInlineDeclarations()) {
+ auto newDoc = util::make_unique<xml::XmlResource>();
+ newDoc->file.config = doc->file.config;
+ newDoc->file.source = doc->file.source.withLine(decl.el->lineNumber);
+ newDoc->file.name = doc->file.name;
+
+ // Modify the new entry name. We need to suffix the entry with a number to
+ // avoid
+ // local collisions, then mangle it with the empty package, such that it
+ // won't show up
+ // in R.java.
+
+ newDoc->file.name.entry = NameMangler::mangleEntry(
+ {}, newDoc->file.name.entry + "__" + std::to_string(nameSuffixCounter));
+
+ // Extracted elements must be the only child of <aapt:attr>.
+ // Make sure there is one root node in the children (ignore empty text).
+ for (auto& child : decl.el->children) {
+ const Source childSource = doc->file.source.withLine(child->lineNumber);
+ if (xml::Text* t = xml::nodeCast<xml::Text>(child.get())) {
+ if (!util::trimWhitespace(t->text).empty()) {
+ context->getDiagnostics()->error(
+ DiagMessage(childSource)
+ << "can't extract text into its own resource");
+ return false;
+ }
+ } else if (newDoc->root) {
+ context->getDiagnostics()->error(
+ DiagMessage(childSource)
+ << "inline XML resources must have a single root");
return false;
+ } else {
+ newDoc->root = std::move(child);
+ newDoc->root->parent = nullptr;
+ }
}
- size_t nameSuffixCounter = 0;
- for (const Visitor::InlineDeclaration& decl : visitor.getInlineDeclarations()) {
- auto newDoc = util::make_unique<xml::XmlResource>();
- newDoc->file.config = doc->file.config;
- newDoc->file.source = doc->file.source.withLine(decl.el->lineNumber);
- newDoc->file.name = doc->file.name;
-
- // Modify the new entry name. We need to suffix the entry with a number to avoid
- // local collisions, then mangle it with the empty package, such that it won't show up
- // in R.java.
-
- newDoc->file.name.entry = NameMangler::mangleEntry(
- {}, newDoc->file.name.entry + "__" + std::to_string(nameSuffixCounter));
-
- // Extracted elements must be the only child of <aapt:attr>.
- // Make sure there is one root node in the children (ignore empty text).
- for (auto& child : decl.el->children) {
- const Source childSource = doc->file.source.withLine(child->lineNumber);
- if (xml::Text* t = xml::nodeCast<xml::Text>(child.get())) {
- if (!util::trimWhitespace(t->text).empty()) {
- context->getDiagnostics()->error(DiagMessage(childSource)
- << "can't extract text into its own resource");
- return false;
- }
- } else if (newDoc->root) {
- context->getDiagnostics()->error(DiagMessage(childSource)
- << "inline XML resources must have a single root");
- return false;
- } else {
- newDoc->root = std::move(child);
- newDoc->root->parent = nullptr;
- }
- }
-
- // Walk up and find the parent element.
- xml::Node* node = decl.el;
- xml::Element* parentEl = nullptr;
- while (node->parent && (parentEl = xml::nodeCast<xml::Element>(node->parent)) == nullptr) {
- node = node->parent;
- }
-
- if (!parentEl) {
- context->getDiagnostics()->error(DiagMessage(newDoc->file.source)
- << "no suitable parent for inheriting attribute");
- return false;
- }
-
- // Add the inline attribute to the parent.
- parentEl->attributes.push_back(xml::Attribute{
- decl.attrNamespaceUri, decl.attrName, "@" + newDoc->file.name.toString() });
-
- // Delete the subtree.
- for (auto iter = parentEl->children.begin(); iter != parentEl->children.end(); ++iter) {
- if (iter->get() == node) {
- parentEl->children.erase(iter);
- break;
- }
- }
-
- mQueue.push_back(std::move(newDoc));
-
- nameSuffixCounter++;
+ // Walk up and find the parent element.
+ xml::Node* node = decl.el;
+ xml::Element* parentEl = nullptr;
+ while (node->parent &&
+ (parentEl = xml::nodeCast<xml::Element>(node->parent)) == nullptr) {
+ node = node->parent;
}
- return true;
+
+ if (!parentEl) {
+ context->getDiagnostics()->error(
+ DiagMessage(newDoc->file.source)
+ << "no suitable parent for inheriting attribute");
+ return false;
+ }
+
+ // Add the inline attribute to the parent.
+ parentEl->attributes.push_back(
+ xml::Attribute{decl.attrNamespaceUri, decl.attrName,
+ "@" + newDoc->file.name.toString()});
+
+ // Delete the subtree.
+ for (auto iter = parentEl->children.begin();
+ iter != parentEl->children.end(); ++iter) {
+ if (iter->get() == node) {
+ parentEl->children.erase(iter);
+ break;
+ }
+ }
+
+ mQueue.push_back(std::move(newDoc));
+
+ nameSuffixCounter++;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h
index 69065fd..cd8794b 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.h
+++ b/tools/aapt2/compile/InlineXmlFormatParser.h
@@ -41,25 +41,28 @@
* </aapt:attr>
* </animated-vector>
*
- * The <vector> will be extracted into its own XML file and <animated-vector> will
- * gain an attribute 'android:drawable' set to a reference to the extracted <vector> resource.
+ * The <vector> will be extracted into its own XML file and <animated-vector>
+ * will
+ * gain an attribute 'android:drawable' set to a reference to the extracted
+ * <vector> resource.
*/
class InlineXmlFormatParser : public IXmlResourceConsumer {
-public:
- explicit InlineXmlFormatParser() = default;
+ public:
+ explicit InlineXmlFormatParser() = default;
- bool consume(IAaptContext* context, xml::XmlResource* doc) override;
+ bool consume(IAaptContext* context, xml::XmlResource* doc) override;
- std::vector<std::unique_ptr<xml::XmlResource>>& getExtractedInlineXmlDocuments() {
- return mQueue;
- }
+ std::vector<std::unique_ptr<xml::XmlResource>>&
+ getExtractedInlineXmlDocuments() {
+ return mQueue;
+ }
-private:
- DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser);
- std::vector<std::unique_ptr<xml::XmlResource>> mQueue;
+ std::vector<std::unique_ptr<xml::XmlResource>> mQueue;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_INLINEXMLFORMATPARSER_H */
diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
index 8d62210..4adb21c 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
+++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
@@ -20,22 +20,22 @@
namespace aapt {
TEST(InlineXmlFormatParserTest, PassThrough) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android">
<View android:text="hey">
<View android:id="hi" />
</View>
</View>)EOF");
- InlineXmlFormatParser parser;
- ASSERT_TRUE(parser.consume(context.get(), doc.get()));
- EXPECT_EQ(0u, parser.getExtractedInlineXmlDocuments().size());
+ InlineXmlFormatParser parser;
+ ASSERT_TRUE(parser.consume(context.get(), doc.get()));
+ EXPECT_EQ(0u, parser.getExtractedInlineXmlDocuments().size());
}
TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View1 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:text">
@@ -45,47 +45,48 @@
</aapt:attr>
</View1>)EOF");
- doc->file.name = test::parseNameOrDie("layout/main");
+ doc->file.name = test::parseNameOrDie("layout/main");
- InlineXmlFormatParser parser;
- ASSERT_TRUE(parser.consume(context.get(), doc.get()));
+ InlineXmlFormatParser parser;
+ ASSERT_TRUE(parser.consume(context.get(), doc.get()));
- // One XML resource should have been extracted.
- EXPECT_EQ(1u, parser.getExtractedInlineXmlDocuments().size());
+ // One XML resource should have been extracted.
+ EXPECT_EQ(1u, parser.getExtractedInlineXmlDocuments().size());
- xml::Element* el = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, el);
+ xml::Element* el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
- EXPECT_EQ("View1", el->name);
+ EXPECT_EQ("View1", el->name);
- // The <aapt:attr> tag should be extracted.
- EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
+ // The <aapt:attr> tag should be extracted.
+ EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
- // The 'android:text' attribute should be set with a reference.
- xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "text");
- ASSERT_NE(nullptr, attr);
+ // The 'android:text' attribute should be set with a reference.
+ xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "text");
+ ASSERT_NE(nullptr, attr);
- ResourceNameRef nameRef;
- ASSERT_TRUE(ResourceUtils::parseReference(attr->value, &nameRef));
+ ResourceNameRef nameRef;
+ ASSERT_TRUE(ResourceUtils::parseReference(attr->value, &nameRef));
- xml::XmlResource* extractedDoc = parser.getExtractedInlineXmlDocuments()[0].get();
- ASSERT_NE(nullptr, extractedDoc);
+ xml::XmlResource* extractedDoc =
+ parser.getExtractedInlineXmlDocuments()[0].get();
+ ASSERT_NE(nullptr, extractedDoc);
- // Make sure the generated reference is correct.
- EXPECT_EQ(nameRef.package, extractedDoc->file.name.package);
- EXPECT_EQ(nameRef.type, extractedDoc->file.name.type);
- EXPECT_EQ(nameRef.entry, extractedDoc->file.name.entry);
+ // Make sure the generated reference is correct.
+ EXPECT_EQ(nameRef.package, extractedDoc->file.name.package);
+ EXPECT_EQ(nameRef.type, extractedDoc->file.name.type);
+ EXPECT_EQ(nameRef.entry, extractedDoc->file.name.entry);
- // Verify the structure of the extracted XML.
- el = xml::findRootElement(extractedDoc);
- ASSERT_NE(nullptr, el);
- EXPECT_EQ("View2", el->name);
- EXPECT_NE(nullptr, el->findChild({}, "View3"));
+ // Verify the structure of the extracted XML.
+ el = xml::findRootElement(extractedDoc);
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ("View2", el->name);
+ EXPECT_NE(nullptr, el->findChild({}, "View3"));
}
TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View1 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:text">
@@ -99,40 +100,43 @@
</aapt:attr>
</View1>)EOF");
- doc->file.name = test::parseNameOrDie("layout/main");
+ doc->file.name = test::parseNameOrDie("layout/main");
- InlineXmlFormatParser parser;
- ASSERT_TRUE(parser.consume(context.get(), doc.get()));
- ASSERT_EQ(2u, parser.getExtractedInlineXmlDocuments().size());
+ InlineXmlFormatParser parser;
+ ASSERT_TRUE(parser.consume(context.get(), doc.get()));
+ ASSERT_EQ(2u, parser.getExtractedInlineXmlDocuments().size());
- xml::Element* el = xml::findRootElement(doc.get());
- ASSERT_NE(nullptr, el);
+ xml::Element* el = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, el);
- EXPECT_EQ("View1", el->name);
+ EXPECT_EQ("View1", el->name);
- xml::Attribute* attrText = el->findAttribute(xml::kSchemaAndroid, "text");
- ASSERT_NE(nullptr, attrText);
+ xml::Attribute* attrText = el->findAttribute(xml::kSchemaAndroid, "text");
+ ASSERT_NE(nullptr, attrText);
- xml::Attribute* attrDrawable = el->findAttribute(xml::kSchemaAndroid, "drawable");
- ASSERT_NE(nullptr, attrDrawable);
+ xml::Attribute* attrDrawable =
+ el->findAttribute(xml::kSchemaAndroid, "drawable");
+ ASSERT_NE(nullptr, attrDrawable);
- // The two extracted resources should have different names.
- EXPECT_NE(attrText->value, attrDrawable->value);
+ // The two extracted resources should have different names.
+ EXPECT_NE(attrText->value, attrDrawable->value);
- // The child <aapt:attr> elements should be gone.
- EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
+ // The child <aapt:attr> elements should be gone.
+ EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr"));
- xml::XmlResource* extractedDocText = parser.getExtractedInlineXmlDocuments()[0].get();
- ASSERT_NE(nullptr, extractedDocText);
- el = xml::findRootElement(extractedDocText);
- ASSERT_NE(nullptr, el);
- EXPECT_EQ("View2", el->name);
+ xml::XmlResource* extractedDocText =
+ parser.getExtractedInlineXmlDocuments()[0].get();
+ ASSERT_NE(nullptr, extractedDocText);
+ el = xml::findRootElement(extractedDocText);
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ("View2", el->name);
- xml::XmlResource* extractedDocDrawable = parser.getExtractedInlineXmlDocuments()[1].get();
- ASSERT_NE(nullptr, extractedDocDrawable);
- el = xml::findRootElement(extractedDocDrawable);
- ASSERT_NE(nullptr, el);
- EXPECT_EQ("vector", el->name);
+ xml::XmlResource* extractedDocDrawable =
+ parser.getExtractedInlineXmlDocuments()[1].get();
+ ASSERT_NE(nullptr, extractedDocDrawable);
+ el = xml::findRootElement(extractedDocDrawable);
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ("vector", el->name);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp
index 0fc1c5d..8842eb7 100644
--- a/tools/aapt2/compile/NinePatch.cpp
+++ b/tools/aapt2/compile/NinePatch.cpp
@@ -28,7 +28,7 @@
// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u;
-constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
+constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u;
constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack;
constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed;
@@ -46,35 +46,37 @@
* but we need to ensure consistency throughout the image.
*/
class ColorValidator {
-public:
- virtual ~ColorValidator() = default;
+ public:
+ virtual ~ColorValidator() = default;
- /**
- * Returns true if the color specified is a neutral color
- * (no padding, stretching, or optical bounds).
- */
- virtual bool isNeutralColor(uint32_t color) const = 0;
+ /**
+ * Returns true if the color specified is a neutral color
+ * (no padding, stretching, or optical bounds).
+ */
+ virtual bool isNeutralColor(uint32_t color) const = 0;
- /**
- * Returns true if the color is either a neutral color
- * or one denoting padding, stretching, or optical bounds.
- */
- bool isValidColor(uint32_t color) const {
- switch (color) {
- case kPrimaryColor:
- case kSecondaryColor:
- return true;
- }
- return isNeutralColor(color);
+ /**
+ * Returns true if the color is either a neutral color
+ * or one denoting padding, stretching, or optical bounds.
+ */
+ bool isValidColor(uint32_t color) const {
+ switch (color) {
+ case kPrimaryColor:
+ case kSecondaryColor:
+ return true;
}
+ return isNeutralColor(color);
+ }
};
// Walks an ImageLine and records Ranges of primary and secondary colors.
-// The primary color is black and is used to denote a padding or stretching range,
+// The primary color is black and is used to denote a padding or stretching
+// range,
// depending on which border we're iterating over.
// The secondary color is red and is used to denote optical bounds.
//
-// An ImageLine is a templated-interface that would look something like this if it
+// An ImageLine is a templated-interface that would look something like this if
+// it
// were polymorphic:
//
// class ImageLine {
@@ -87,590 +89,604 @@
static bool fillRanges(const ImageLine* imageLine,
const ColorValidator* colorValidator,
std::vector<Range>* primaryRanges,
- std::vector<Range>* secondaryRanges,
- std::string* err) {
- const int32_t length = imageLine->getLength();
+ std::vector<Range>* secondaryRanges, std::string* err) {
+ const int32_t length = imageLine->getLength();
- uint32_t lastColor = 0xffffffffu;
- for (int32_t idx = 1; idx < length - 1; idx++) {
- const uint32_t color = imageLine->getColor(idx);
- if (!colorValidator->isValidColor(color)) {
- *err = "found an invalid color";
- return false;
- }
-
- if (color != lastColor) {
- // We are ending a range. Which range?
- // note: encode the x offset without the final 1 pixel border.
- if (lastColor == kPrimaryColor) {
- primaryRanges->back().end = idx - 1;
- } else if (lastColor == kSecondaryColor) {
- secondaryRanges->back().end = idx - 1;
- }
-
- // We are starting a range. Which range?
- // note: encode the x offset without the final 1 pixel border.
- if (color == kPrimaryColor) {
- primaryRanges->push_back(Range(idx - 1, length - 2));
- } else if (color == kSecondaryColor) {
- secondaryRanges->push_back(Range(idx - 1, length - 2));
- }
- lastColor = color;
- }
+ uint32_t lastColor = 0xffffffffu;
+ for (int32_t idx = 1; idx < length - 1; idx++) {
+ const uint32_t color = imageLine->getColor(idx);
+ if (!colorValidator->isValidColor(color)) {
+ *err = "found an invalid color";
+ return false;
}
- return true;
+
+ if (color != lastColor) {
+ // We are ending a range. Which range?
+ // note: encode the x offset without the final 1 pixel border.
+ if (lastColor == kPrimaryColor) {
+ primaryRanges->back().end = idx - 1;
+ } else if (lastColor == kSecondaryColor) {
+ secondaryRanges->back().end = idx - 1;
+ }
+
+ // We are starting a range. Which range?
+ // note: encode the x offset without the final 1 pixel border.
+ if (color == kPrimaryColor) {
+ primaryRanges->push_back(Range(idx - 1, length - 2));
+ } else if (color == kSecondaryColor) {
+ secondaryRanges->push_back(Range(idx - 1, length - 2));
+ }
+ lastColor = color;
+ }
+ }
+ return true;
}
/**
- * Iterates over a row in an image. Implements the templated ImageLine interface.
+ * Iterates over a row in an image. Implements the templated ImageLine
+ * interface.
*/
class HorizontalImageLine {
-public:
- explicit HorizontalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
- int32_t length) :
- mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {
- }
+ public:
+ explicit HorizontalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
+ int32_t length)
+ : mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {}
- inline int32_t getLength() const {
- return mLength;
- }
+ inline int32_t getLength() const { return mLength; }
- inline uint32_t getColor(int32_t idx) const {
- return NinePatch::packRGBA(mRows[mYOffset] + (idx + mXOffset) * 4);
- }
+ inline uint32_t getColor(int32_t idx) const {
+ return NinePatch::packRGBA(mRows[mYOffset] + (idx + mXOffset) * 4);
+ }
-private:
- uint8_t** mRows;
- int32_t mXOffset, mYOffset, mLength;
+ private:
+ uint8_t** mRows;
+ int32_t mXOffset, mYOffset, mLength;
- DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
+ DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine);
};
/**
- * Iterates over a column in an image. Implements the templated ImageLine interface.
+ * Iterates over a column in an image. Implements the templated ImageLine
+ * interface.
*/
class VerticalImageLine {
-public:
- explicit VerticalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
- int32_t length) :
- mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {
- }
+ public:
+ explicit VerticalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
+ int32_t length)
+ : mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {}
- inline int32_t getLength() const {
- return mLength;
- }
+ inline int32_t getLength() const { return mLength; }
- inline uint32_t getColor(int32_t idx) const {
- return NinePatch::packRGBA(mRows[mYOffset + idx] + (mXOffset * 4));
- }
+ inline uint32_t getColor(int32_t idx) const {
+ return NinePatch::packRGBA(mRows[mYOffset + idx] + (mXOffset * 4));
+ }
-private:
- uint8_t** mRows;
- int32_t mXOffset, mYOffset, mLength;
+ private:
+ uint8_t** mRows;
+ int32_t mXOffset, mYOffset, mLength;
- DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
+ DISALLOW_COPY_AND_ASSIGN(VerticalImageLine);
};
class DiagonalImageLine {
-public:
- explicit DiagonalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
- int32_t xStep, int32_t yStep, int32_t length) :
- mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mXStep(xStep), mYStep(yStep),
- mLength(length) {
- }
+ public:
+ explicit DiagonalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset,
+ int32_t xStep, int32_t yStep, int32_t length)
+ : mRows(rows),
+ mXOffset(xOffset),
+ mYOffset(yOffset),
+ mXStep(xStep),
+ mYStep(yStep),
+ mLength(length) {}
- inline int32_t getLength() const {
- return mLength;
- }
+ inline int32_t getLength() const { return mLength; }
- inline uint32_t getColor(int32_t idx) const {
- return NinePatch::packRGBA(
- mRows[mYOffset + (idx * mYStep)] + ((idx + mXOffset) * mXStep) * 4);
- }
+ inline uint32_t getColor(int32_t idx) const {
+ return NinePatch::packRGBA(mRows[mYOffset + (idx * mYStep)] +
+ ((idx + mXOffset) * mXStep) * 4);
+ }
-private:
- uint8_t** mRows;
- int32_t mXOffset, mYOffset, mXStep, mYStep, mLength;
+ private:
+ uint8_t** mRows;
+ int32_t mXOffset, mYOffset, mXStep, mYStep, mLength;
- DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
+ DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine);
};
class TransparentNeutralColorValidator : public ColorValidator {
-public:
- bool isNeutralColor(uint32_t color) const override {
- return getAlpha(color) == 0;
- }
+ public:
+ bool isNeutralColor(uint32_t color) const override {
+ return getAlpha(color) == 0;
+ }
};
class WhiteNeutralColorValidator : public ColorValidator {
-public:
- bool isNeutralColor(uint32_t color) const override {
- return color == kColorOpaqueWhite;
- }
+ public:
+ bool isNeutralColor(uint32_t color) const override {
+ return color == kColorOpaqueWhite;
+ }
};
inline static uint32_t getAlpha(uint32_t color) {
- return (color & 0xff000000u) >> 24;
+ return (color & 0xff000000u) >> 24;
}
static bool populateBounds(const std::vector<Range>& padding,
const std::vector<Range>& layoutBounds,
const std::vector<Range>& stretchRegions,
- const int32_t length,
- int32_t* paddingStart, int32_t* paddingEnd,
- int32_t* layoutStart, int32_t* layoutEnd,
- const StringPiece& edgeName,
+ const int32_t length, int32_t* paddingStart,
+ int32_t* paddingEnd, int32_t* layoutStart,
+ int32_t* layoutEnd, const StringPiece& edgeName,
std::string* err) {
- if (padding.size() > 1) {
+ if (padding.size() > 1) {
+ std::stringstream errStream;
+ errStream << "too many padding sections on " << edgeName << " border";
+ *err = errStream.str();
+ return false;
+ }
+
+ *paddingStart = 0;
+ *paddingEnd = 0;
+ if (!padding.empty()) {
+ const Range& range = padding.front();
+ *paddingStart = range.start;
+ *paddingEnd = length - range.end;
+ } else if (!stretchRegions.empty()) {
+ // No padding was defined. Compute the padding from the first and last
+ // stretch regions.
+ *paddingStart = stretchRegions.front().start;
+ *paddingEnd = length - stretchRegions.back().end;
+ }
+
+ if (layoutBounds.size() > 2) {
+ std::stringstream errStream;
+ errStream << "too many layout bounds sections on " << edgeName << " border";
+ *err = errStream.str();
+ return false;
+ }
+
+ *layoutStart = 0;
+ *layoutEnd = 0;
+ if (layoutBounds.size() >= 1) {
+ const Range& range = layoutBounds.front();
+ // If there is only one layout bound segment, it might not start at 0, but
+ // then it should
+ // end at length.
+ if (range.start != 0 && range.end != length) {
+ std::stringstream errStream;
+ errStream << "layout bounds on " << edgeName
+ << " border must start at edge";
+ *err = errStream.str();
+ return false;
+ }
+ *layoutStart = range.end;
+
+ if (layoutBounds.size() >= 2) {
+ const Range& range = layoutBounds.back();
+ if (range.end != length) {
std::stringstream errStream;
- errStream << "too many padding sections on " << edgeName << " border";
+ errStream << "layout bounds on " << edgeName
+ << " border must start at edge";
*err = errStream.str();
return false;
+ }
+ *layoutEnd = length - range.start;
}
-
- *paddingStart = 0;
- *paddingEnd = 0;
- if (!padding.empty()) {
- const Range& range = padding.front();
- *paddingStart = range.start;
- *paddingEnd = length - range.end;
- } else if (!stretchRegions.empty()) {
- // No padding was defined. Compute the padding from the first and last
- // stretch regions.
- *paddingStart = stretchRegions.front().start;
- *paddingEnd = length - stretchRegions.back().end;
- }
-
- if (layoutBounds.size() > 2) {
- std::stringstream errStream;
- errStream << "too many layout bounds sections on " << edgeName << " border";
- *err = errStream.str();
- return false;
- }
-
- *layoutStart = 0;
- *layoutEnd = 0;
- if (layoutBounds.size() >= 1) {
- const Range& range = layoutBounds.front();
- // If there is only one layout bound segment, it might not start at 0, but then it should
- // end at length.
- if (range.start != 0 && range.end != length) {
- std::stringstream errStream;
- errStream << "layout bounds on " << edgeName << " border must start at edge";
- *err = errStream.str();
- return false;
- }
- *layoutStart = range.end;
-
- if (layoutBounds.size() >= 2) {
- const Range& range = layoutBounds.back();
- if (range.end != length) {
- std::stringstream errStream;
- errStream << "layout bounds on " << edgeName << " border must start at edge";
- *err = errStream.str();
- return false;
- }
- *layoutEnd = length - range.start;
- }
- }
- return true;
+ }
+ return true;
}
-static int32_t calculateSegmentCount(const std::vector<Range>& stretchRegions, int32_t length) {
- if (stretchRegions.size() == 0) {
- return 0;
- }
+static int32_t calculateSegmentCount(const std::vector<Range>& stretchRegions,
+ int32_t length) {
+ if (stretchRegions.size() == 0) {
+ return 0;
+ }
- const bool startIsFixed = stretchRegions.front().start != 0;
- const bool endIsFixed = stretchRegions.back().end != length;
- int32_t modifier = 0;
- if (startIsFixed && endIsFixed) {
- modifier = 1;
- } else if (!startIsFixed && !endIsFixed) {
- modifier = -1;
- }
- return static_cast<int32_t>(stretchRegions.size()) * 2 + modifier;
+ const bool startIsFixed = stretchRegions.front().start != 0;
+ const bool endIsFixed = stretchRegions.back().end != length;
+ int32_t modifier = 0;
+ if (startIsFixed && endIsFixed) {
+ modifier = 1;
+ } else if (!startIsFixed && !endIsFixed) {
+ modifier = -1;
+ }
+ return static_cast<int32_t>(stretchRegions.size()) * 2 + modifier;
}
static uint32_t getRegionColor(uint8_t** rows, const Bounds& region) {
- // Sample the first pixel to compare against.
- const uint32_t expectedColor = NinePatch::packRGBA(rows[region.top] + region.left * 4);
- for (int32_t y = region.top; y < region.bottom; y++) {
- const uint8_t* row = rows[y];
- for (int32_t x = region.left; x < region.right; x++) {
- const uint32_t color = NinePatch::packRGBA(row + x * 4);
- if (getAlpha(color) == 0) {
- // The color is transparent.
- // If the expectedColor is not transparent, NO_COLOR.
- if (getAlpha(expectedColor) != 0) {
- return android::Res_png_9patch::NO_COLOR;
- }
- } else if (color != expectedColor) {
- return android::Res_png_9patch::NO_COLOR;
- }
+ // Sample the first pixel to compare against.
+ const uint32_t expectedColor =
+ NinePatch::packRGBA(rows[region.top] + region.left * 4);
+ for (int32_t y = region.top; y < region.bottom; y++) {
+ const uint8_t* row = rows[y];
+ for (int32_t x = region.left; x < region.right; x++) {
+ const uint32_t color = NinePatch::packRGBA(row + x * 4);
+ if (getAlpha(color) == 0) {
+ // The color is transparent.
+ // If the expectedColor is not transparent, NO_COLOR.
+ if (getAlpha(expectedColor) != 0) {
+ return android::Res_png_9patch::NO_COLOR;
}
+ } else if (color != expectedColor) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
}
+ }
- if (getAlpha(expectedColor) == 0) {
- return android::Res_png_9patch::TRANSPARENT_COLOR;
- }
- return expectedColor;
+ if (getAlpha(expectedColor) == 0) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return expectedColor;
}
-// Fills outColors with each 9-patch section's colour. If the whole section is transparent,
-// it gets the special TRANSPARENT colour. If the whole section is the same colour, it is assigned
+// Fills outColors with each 9-patch section's colour. If the whole section is
+// transparent,
+// it gets the special TRANSPARENT colour. If the whole section is the same
+// colour, it is assigned
// that colour. Otherwise it gets the special NO_COLOR colour.
//
-// Note that the rows contain the 9-patch 1px border, and the indices in the stretch regions are
-// already offset to exclude the border. This means that each time the rows are accessed,
+// Note that the rows contain the 9-patch 1px border, and the indices in the
+// stretch regions are
+// already offset to exclude the border. This means that each time the rows are
+// accessed,
// the indices must be offset by 1.
//
// width and height also include the 9-patch 1px border.
-static void calculateRegionColors(uint8_t** rows,
- const std::vector<Range>& horizontalStretchRegions,
- const std::vector<Range>& verticalStretchRegions,
- const int32_t width, const int32_t height,
- std::vector<uint32_t>* outColors) {
- int32_t nextTop = 0;
- Bounds bounds;
- auto rowIter = verticalStretchRegions.begin();
- while (nextTop != height) {
- if (rowIter != verticalStretchRegions.end()) {
- if (nextTop != rowIter->start) {
- // This is a fixed segment.
- // Offset the bounds by 1 to accommodate the border.
- bounds.top = nextTop + 1;
- bounds.bottom = rowIter->start + 1;
- nextTop = rowIter->start;
- } else {
- // This is a stretchy segment.
- // Offset the bounds by 1 to accommodate the border.
- bounds.top = rowIter->start + 1;
- bounds.bottom = rowIter->end + 1;
- nextTop = rowIter->end;
- ++rowIter;
- }
- } else {
- // This is the end, fixed section.
- // Offset the bounds by 1 to accommodate the border.
- bounds.top = nextTop + 1;
- bounds.bottom = height + 1;
- nextTop = height;
- }
-
- int32_t nextLeft = 0;
- auto colIter = horizontalStretchRegions.begin();
- while (nextLeft != width) {
- if (colIter != horizontalStretchRegions.end()) {
- if (nextLeft != colIter->start) {
- // This is a fixed segment.
- // Offset the bounds by 1 to accommodate the border.
- bounds.left = nextLeft + 1;
- bounds.right = colIter->start + 1;
- nextLeft = colIter->start;
- } else {
- // This is a stretchy segment.
- // Offset the bounds by 1 to accommodate the border.
- bounds.left = colIter->start + 1;
- bounds.right = colIter->end + 1;
- nextLeft = colIter->end;
- ++colIter;
- }
- } else {
- // This is the end, fixed section.
- // Offset the bounds by 1 to accommodate the border.
- bounds.left = nextLeft + 1;
- bounds.right = width + 1;
- nextLeft = width;
- }
- outColors->push_back(getRegionColor(rows, bounds));
- }
+static void calculateRegionColors(
+ uint8_t** rows, const std::vector<Range>& horizontalStretchRegions,
+ const std::vector<Range>& verticalStretchRegions, const int32_t width,
+ const int32_t height, std::vector<uint32_t>* outColors) {
+ int32_t nextTop = 0;
+ Bounds bounds;
+ auto rowIter = verticalStretchRegions.begin();
+ while (nextTop != height) {
+ if (rowIter != verticalStretchRegions.end()) {
+ if (nextTop != rowIter->start) {
+ // This is a fixed segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = nextTop + 1;
+ bounds.bottom = rowIter->start + 1;
+ nextTop = rowIter->start;
+ } else {
+ // This is a stretchy segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = rowIter->start + 1;
+ bounds.bottom = rowIter->end + 1;
+ nextTop = rowIter->end;
+ ++rowIter;
+ }
+ } else {
+ // This is the end, fixed section.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.top = nextTop + 1;
+ bounds.bottom = height + 1;
+ nextTop = height;
}
+
+ int32_t nextLeft = 0;
+ auto colIter = horizontalStretchRegions.begin();
+ while (nextLeft != width) {
+ if (colIter != horizontalStretchRegions.end()) {
+ if (nextLeft != colIter->start) {
+ // This is a fixed segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = nextLeft + 1;
+ bounds.right = colIter->start + 1;
+ nextLeft = colIter->start;
+ } else {
+ // This is a stretchy segment.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = colIter->start + 1;
+ bounds.right = colIter->end + 1;
+ nextLeft = colIter->end;
+ ++colIter;
+ }
+ } else {
+ // This is the end, fixed section.
+ // Offset the bounds by 1 to accommodate the border.
+ bounds.left = nextLeft + 1;
+ bounds.right = width + 1;
+ nextLeft = width;
+ }
+ outColors->push_back(getRegionColor(rows, bounds));
+ }
+ }
}
-// Calculates the insets of a row/column of pixels based on where the largest alpha value begins
+// Calculates the insets of a row/column of pixels based on where the largest
+// alpha value begins
// (on both sides).
template <typename ImageLine>
-static void findOutlineInsets(const ImageLine* imageLine, int32_t* outStart, int32_t* outEnd) {
- *outStart = 0;
- *outEnd = 0;
+static void findOutlineInsets(const ImageLine* imageLine, int32_t* outStart,
+ int32_t* outEnd) {
+ *outStart = 0;
+ *outEnd = 0;
- const int32_t length = imageLine->getLength();
- if (length < 3) {
- return;
- }
-
- // If the length is odd, we want both sides to process the center pixel,
- // so we use two different midpoints (to account for < and <= in the different loops).
- const int32_t mid2 = length / 2;
- const int32_t mid1 = mid2 + (length % 2);
-
- uint32_t maxAlpha = 0;
- for (int32_t i = 0; i < mid1 && maxAlpha != 0xff; i++) {
- uint32_t alpha = getAlpha(imageLine->getColor(i));
- if (alpha > maxAlpha) {
- maxAlpha = alpha;
- *outStart = i;
- }
- }
-
- maxAlpha = 0;
- for (int32_t i = length - 1; i >= mid2 && maxAlpha != 0xff; i--) {
- uint32_t alpha = getAlpha(imageLine->getColor(i));
- if (alpha > maxAlpha) {
- maxAlpha = alpha;
- *outEnd = length - (i + 1);
- }
- }
+ const int32_t length = imageLine->getLength();
+ if (length < 3) {
return;
+ }
+
+ // If the length is odd, we want both sides to process the center pixel,
+ // so we use two different midpoints (to account for < and <= in the different
+ // loops).
+ const int32_t mid2 = length / 2;
+ const int32_t mid1 = mid2 + (length % 2);
+
+ uint32_t maxAlpha = 0;
+ for (int32_t i = 0; i < mid1 && maxAlpha != 0xff; i++) {
+ uint32_t alpha = getAlpha(imageLine->getColor(i));
+ if (alpha > maxAlpha) {
+ maxAlpha = alpha;
+ *outStart = i;
+ }
+ }
+
+ maxAlpha = 0;
+ for (int32_t i = length - 1; i >= mid2 && maxAlpha != 0xff; i--) {
+ uint32_t alpha = getAlpha(imageLine->getColor(i));
+ if (alpha > maxAlpha) {
+ maxAlpha = alpha;
+ *outEnd = length - (i + 1);
+ }
+ }
+ return;
}
template <typename ImageLine>
static uint32_t findMaxAlpha(const ImageLine* imageLine) {
- const int32_t length = imageLine->getLength();
- uint32_t maxAlpha = 0;
- for (int32_t idx = 0; idx < length && maxAlpha != 0xff; idx++) {
- uint32_t alpha = getAlpha(imageLine->getColor(idx));
- if (alpha > maxAlpha) {
- maxAlpha = alpha;
- }
+ const int32_t length = imageLine->getLength();
+ uint32_t maxAlpha = 0;
+ for (int32_t idx = 0; idx < length && maxAlpha != 0xff; idx++) {
+ uint32_t alpha = getAlpha(imageLine->getColor(idx));
+ if (alpha > maxAlpha) {
+ maxAlpha = alpha;
}
- return maxAlpha;
+ }
+ return maxAlpha;
}
// Pack the pixels in as 0xAARRGGBB (as 9-patch expects it).
uint32_t NinePatch::packRGBA(const uint8_t* pixel) {
- return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
+ return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
}
std::unique_ptr<NinePatch> NinePatch::create(uint8_t** rows,
- const int32_t width, const int32_t height,
+ const int32_t width,
+ const int32_t height,
std::string* err) {
- if (width < 3 || height < 3) {
- *err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
- return {};
- }
+ if (width < 3 || height < 3) {
+ *err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
+ return {};
+ }
- std::vector<Range> horizontalPadding;
- std::vector<Range> horizontalOpticalBounds;
- std::vector<Range> verticalPadding;
- std::vector<Range> verticalOpticalBounds;
- std::vector<Range> unexpectedRanges;
- std::unique_ptr<ColorValidator> colorValidator;
+ std::vector<Range> horizontalPadding;
+ std::vector<Range> horizontalOpticalBounds;
+ std::vector<Range> verticalPadding;
+ std::vector<Range> verticalOpticalBounds;
+ std::vector<Range> unexpectedRanges;
+ std::unique_ptr<ColorValidator> colorValidator;
- if (rows[0][3] == 0) {
- colorValidator = util::make_unique<TransparentNeutralColorValidator>();
- } else if (packRGBA(rows[0]) == kColorOpaqueWhite) {
- colorValidator = util::make_unique<WhiteNeutralColorValidator>();
- } else {
- *err = "top-left corner pixel must be either opaque white or transparent";
- return {};
- }
+ if (rows[0][3] == 0) {
+ colorValidator = util::make_unique<TransparentNeutralColorValidator>();
+ } else if (packRGBA(rows[0]) == kColorOpaqueWhite) {
+ colorValidator = util::make_unique<WhiteNeutralColorValidator>();
+ } else {
+ *err = "top-left corner pixel must be either opaque white or transparent";
+ return {};
+ }
- // Private constructor, can't use make_unique.
- auto ninePatch = std::unique_ptr<NinePatch>(new NinePatch());
+ // Private constructor, can't use make_unique.
+ auto ninePatch = std::unique_ptr<NinePatch>(new NinePatch());
- HorizontalImageLine topRow(rows, 0, 0, width);
- if (!fillRanges(&topRow, colorValidator.get(), &ninePatch->horizontalStretchRegions,
- &unexpectedRanges, err)) {
- return {};
- }
+ HorizontalImageLine topRow(rows, 0, 0, width);
+ if (!fillRanges(&topRow, colorValidator.get(),
+ &ninePatch->horizontalStretchRegions, &unexpectedRanges,
+ err)) {
+ return {};
+ }
- if (!unexpectedRanges.empty()) {
- const Range& range = unexpectedRanges[0];
- std::stringstream errStream;
- errStream << "found unexpected optical bounds (red pixel) on top border "
- << "at x=" << range.start + 1;
- *err = errStream.str();
- return {};
- }
+ if (!unexpectedRanges.empty()) {
+ const Range& range = unexpectedRanges[0];
+ std::stringstream errStream;
+ errStream << "found unexpected optical bounds (red pixel) on top border "
+ << "at x=" << range.start + 1;
+ *err = errStream.str();
+ return {};
+ }
- VerticalImageLine leftCol(rows, 0, 0, height);
- if (!fillRanges(&leftCol, colorValidator.get(), &ninePatch->verticalStretchRegions,
- &unexpectedRanges, err)) {
- return {};
- }
+ VerticalImageLine leftCol(rows, 0, 0, height);
+ if (!fillRanges(&leftCol, colorValidator.get(),
+ &ninePatch->verticalStretchRegions, &unexpectedRanges, err)) {
+ return {};
+ }
- if (!unexpectedRanges.empty()) {
- const Range& range = unexpectedRanges[0];
- std::stringstream errStream;
- errStream << "found unexpected optical bounds (red pixel) on left border "
- << "at y=" << range.start + 1;
- return {};
- }
+ if (!unexpectedRanges.empty()) {
+ const Range& range = unexpectedRanges[0];
+ std::stringstream errStream;
+ errStream << "found unexpected optical bounds (red pixel) on left border "
+ << "at y=" << range.start + 1;
+ return {};
+ }
- HorizontalImageLine bottomRow(rows, 0, height - 1, width);
- if (!fillRanges(&bottomRow, colorValidator.get(), &horizontalPadding,
- &horizontalOpticalBounds, err)) {
- return {};
- }
+ HorizontalImageLine bottomRow(rows, 0, height - 1, width);
+ if (!fillRanges(&bottomRow, colorValidator.get(), &horizontalPadding,
+ &horizontalOpticalBounds, err)) {
+ return {};
+ }
- if (!populateBounds(horizontalPadding, horizontalOpticalBounds,
- ninePatch->horizontalStretchRegions, width - 2,
- &ninePatch->padding.left, &ninePatch->padding.right,
- &ninePatch->layoutBounds.left, &ninePatch->layoutBounds.right,
- "bottom", err)) {
- return {};
- }
+ if (!populateBounds(horizontalPadding, horizontalOpticalBounds,
+ ninePatch->horizontalStretchRegions, width - 2,
+ &ninePatch->padding.left, &ninePatch->padding.right,
+ &ninePatch->layoutBounds.left,
+ &ninePatch->layoutBounds.right, "bottom", err)) {
+ return {};
+ }
- VerticalImageLine rightCol(rows, width - 1, 0, height);
- if (!fillRanges(&rightCol, colorValidator.get(), &verticalPadding,
- &verticalOpticalBounds, err)) {
- return {};
- }
+ VerticalImageLine rightCol(rows, width - 1, 0, height);
+ if (!fillRanges(&rightCol, colorValidator.get(), &verticalPadding,
+ &verticalOpticalBounds, err)) {
+ return {};
+ }
- if (!populateBounds(verticalPadding, verticalOpticalBounds,
- ninePatch->verticalStretchRegions, height - 2,
- &ninePatch->padding.top, &ninePatch->padding.bottom,
- &ninePatch->layoutBounds.top, &ninePatch->layoutBounds.bottom,
- "right", err)) {
- return {};
- }
+ if (!populateBounds(verticalPadding, verticalOpticalBounds,
+ ninePatch->verticalStretchRegions, height - 2,
+ &ninePatch->padding.top, &ninePatch->padding.bottom,
+ &ninePatch->layoutBounds.top,
+ &ninePatch->layoutBounds.bottom, "right", err)) {
+ return {};
+ }
- // Fill the region colors of the 9-patch.
- const int32_t numRows = calculateSegmentCount(ninePatch->horizontalStretchRegions, width - 2);
- const int32_t numCols = calculateSegmentCount(ninePatch->verticalStretchRegions, height - 2);
- if ((int64_t) numRows * (int64_t) numCols > 0x7f) {
- *err = "too many regions in 9-patch";
- return {};
- }
+ // Fill the region colors of the 9-patch.
+ const int32_t numRows =
+ calculateSegmentCount(ninePatch->horizontalStretchRegions, width - 2);
+ const int32_t numCols =
+ calculateSegmentCount(ninePatch->verticalStretchRegions, height - 2);
+ if ((int64_t)numRows * (int64_t)numCols > 0x7f) {
+ *err = "too many regions in 9-patch";
+ return {};
+ }
- ninePatch->regionColors.reserve(numRows * numCols);
- calculateRegionColors(rows, ninePatch->horizontalStretchRegions,
- ninePatch->verticalStretchRegions,
- width - 2, height - 2,
- &ninePatch->regionColors);
+ ninePatch->regionColors.reserve(numRows * numCols);
+ calculateRegionColors(rows, ninePatch->horizontalStretchRegions,
+ ninePatch->verticalStretchRegions, width - 2,
+ height - 2, &ninePatch->regionColors);
- // Compute the outline based on opacity.
+ // Compute the outline based on opacity.
- // Find left and right extent of 9-patch content on center row.
- HorizontalImageLine midRow(rows, 1, height / 2, width - 2);
- findOutlineInsets(&midRow, &ninePatch->outline.left, &ninePatch->outline.right);
+ // Find left and right extent of 9-patch content on center row.
+ HorizontalImageLine midRow(rows, 1, height / 2, width - 2);
+ findOutlineInsets(&midRow, &ninePatch->outline.left,
+ &ninePatch->outline.right);
- // Find top and bottom extent of 9-patch content on center column.
- VerticalImageLine midCol(rows, width / 2, 1, height - 2);
- findOutlineInsets(&midCol, &ninePatch->outline.top, &ninePatch->outline.bottom);
+ // Find top and bottom extent of 9-patch content on center column.
+ VerticalImageLine midCol(rows, width / 2, 1, height - 2);
+ findOutlineInsets(&midCol, &ninePatch->outline.top,
+ &ninePatch->outline.bottom);
- const int32_t outlineWidth = (width - 2) - ninePatch->outline.left - ninePatch->outline.right;
- const int32_t outlineHeight = (height - 2) - ninePatch->outline.top - ninePatch->outline.bottom;
+ const int32_t outlineWidth =
+ (width - 2) - ninePatch->outline.left - ninePatch->outline.right;
+ const int32_t outlineHeight =
+ (height - 2) - ninePatch->outline.top - ninePatch->outline.bottom;
- // Find the largest alpha value within the outline area.
- HorizontalImageLine outlineMidRow(rows,
- 1 + ninePatch->outline.left,
- 1 + ninePatch->outline.top + (outlineHeight / 2),
- outlineWidth);
- VerticalImageLine outlineMidCol(rows,
- 1 + ninePatch->outline.left + (outlineWidth / 2),
- 1 + ninePatch->outline.top,
- outlineHeight);
- ninePatch->outlineAlpha = std::max(findMaxAlpha(&outlineMidRow), findMaxAlpha(&outlineMidCol));
+ // Find the largest alpha value within the outline area.
+ HorizontalImageLine outlineMidRow(
+ rows, 1 + ninePatch->outline.left,
+ 1 + ninePatch->outline.top + (outlineHeight / 2), outlineWidth);
+ VerticalImageLine outlineMidCol(
+ rows, 1 + ninePatch->outline.left + (outlineWidth / 2),
+ 1 + ninePatch->outline.top, outlineHeight);
+ ninePatch->outlineAlpha =
+ std::max(findMaxAlpha(&outlineMidRow), findMaxAlpha(&outlineMidCol));
- // Assuming the image is a round rect, compute the radius by marching
- // diagonally from the top left corner towards the center.
- DiagonalImageLine diagonal(rows, 1 + ninePatch->outline.left, 1 + ninePatch->outline.top,
- 1, 1, std::min(outlineWidth, outlineHeight));
- int32_t topLeft, bottomRight;
- findOutlineInsets(&diagonal, &topLeft, &bottomRight);
+ // Assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center.
+ DiagonalImageLine diagonal(rows, 1 + ninePatch->outline.left,
+ 1 + ninePatch->outline.top, 1, 1,
+ std::min(outlineWidth, outlineHeight));
+ int32_t topLeft, bottomRight;
+ findOutlineInsets(&diagonal, &topLeft, &bottomRight);
- /* Determine source radius based upon inset:
- * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
- * sqrt(2) * r = sqrt(2) * i + r
- * (sqrt(2) - 1) * r = sqrt(2) * i
- * r = sqrt(2) / (sqrt(2) - 1) * i
- */
- ninePatch->outlineRadius = 3.4142f * topLeft;
- return ninePatch;
+ /* Determine source radius based upon inset:
+ * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+ * sqrt(2) * r = sqrt(2) * i + r
+ * (sqrt(2) - 1) * r = sqrt(2) * i
+ * r = sqrt(2) / (sqrt(2) - 1) * i
+ */
+ ninePatch->outlineRadius = 3.4142f * topLeft;
+ return ninePatch;
}
std::unique_ptr<uint8_t[]> NinePatch::serializeBase(size_t* outLen) const {
- android::Res_png_9patch data;
- data.numXDivs = static_cast<uint8_t>(horizontalStretchRegions.size()) * 2;
- data.numYDivs = static_cast<uint8_t>(verticalStretchRegions.size()) * 2;
- data.numColors = static_cast<uint8_t>(regionColors.size());
- data.paddingLeft = padding.left;
- data.paddingRight = padding.right;
- data.paddingTop = padding.top;
- data.paddingBottom = padding.bottom;
+ android::Res_png_9patch data;
+ data.numXDivs = static_cast<uint8_t>(horizontalStretchRegions.size()) * 2;
+ data.numYDivs = static_cast<uint8_t>(verticalStretchRegions.size()) * 2;
+ data.numColors = static_cast<uint8_t>(regionColors.size());
+ data.paddingLeft = padding.left;
+ data.paddingRight = padding.right;
+ data.paddingTop = padding.top;
+ data.paddingBottom = padding.bottom;
- auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
- android::Res_png_9patch::serialize(data,
- (const int32_t*) horizontalStretchRegions.data(),
- (const int32_t*) verticalStretchRegions.data(),
- regionColors.data(),
- buffer.get());
- // Convert to file endianness.
- reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
+ android::Res_png_9patch::serialize(
+ data, (const int32_t*)horizontalStretchRegions.data(),
+ (const int32_t*)verticalStretchRegions.data(), regionColors.data(),
+ buffer.get());
+ // Convert to file endianness.
+ reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
- *outLen = data.serializedSize();
- return buffer;
+ *outLen = data.serializedSize();
+ return buffer;
}
-std::unique_ptr<uint8_t[]> NinePatch::serializeLayoutBounds(size_t* outLen) const {
- size_t chunkLen = sizeof(uint32_t) * 4;
- auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
- uint8_t* cursor = buffer.get();
+std::unique_ptr<uint8_t[]> NinePatch::serializeLayoutBounds(
+ size_t* outLen) const {
+ size_t chunkLen = sizeof(uint32_t) * 4;
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
+ uint8_t* cursor = buffer.get();
- memcpy(cursor, &layoutBounds.left, sizeof(layoutBounds.left));
- cursor += sizeof(layoutBounds.left);
+ memcpy(cursor, &layoutBounds.left, sizeof(layoutBounds.left));
+ cursor += sizeof(layoutBounds.left);
- memcpy(cursor, &layoutBounds.top, sizeof(layoutBounds.top));
- cursor += sizeof(layoutBounds.top);
+ memcpy(cursor, &layoutBounds.top, sizeof(layoutBounds.top));
+ cursor += sizeof(layoutBounds.top);
- memcpy(cursor, &layoutBounds.right, sizeof(layoutBounds.right));
- cursor += sizeof(layoutBounds.right);
+ memcpy(cursor, &layoutBounds.right, sizeof(layoutBounds.right));
+ cursor += sizeof(layoutBounds.right);
- memcpy(cursor, &layoutBounds.bottom, sizeof(layoutBounds.bottom));
- cursor += sizeof(layoutBounds.bottom);
+ memcpy(cursor, &layoutBounds.bottom, sizeof(layoutBounds.bottom));
+ cursor += sizeof(layoutBounds.bottom);
- *outLen = chunkLen;
- return buffer;
+ *outLen = chunkLen;
+ return buffer;
}
-std::unique_ptr<uint8_t[]> NinePatch::serializeRoundedRectOutline(size_t* outLen) const {
- size_t chunkLen = sizeof(uint32_t) * 6;
- auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
- uint8_t* cursor = buffer.get();
+std::unique_ptr<uint8_t[]> NinePatch::serializeRoundedRectOutline(
+ size_t* outLen) const {
+ size_t chunkLen = sizeof(uint32_t) * 6;
+ auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]);
+ uint8_t* cursor = buffer.get();
- memcpy(cursor, &outline.left, sizeof(outline.left));
- cursor += sizeof(outline.left);
+ memcpy(cursor, &outline.left, sizeof(outline.left));
+ cursor += sizeof(outline.left);
- memcpy(cursor, &outline.top, sizeof(outline.top));
- cursor += sizeof(outline.top);
+ memcpy(cursor, &outline.top, sizeof(outline.top));
+ cursor += sizeof(outline.top);
- memcpy(cursor, &outline.right, sizeof(outline.right));
- cursor += sizeof(outline.right);
+ memcpy(cursor, &outline.right, sizeof(outline.right));
+ cursor += sizeof(outline.right);
- memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
- cursor += sizeof(outline.bottom);
+ memcpy(cursor, &outline.bottom, sizeof(outline.bottom));
+ cursor += sizeof(outline.bottom);
- *((float*) cursor) = outlineRadius;
- cursor += sizeof(outlineRadius);
+ *((float*)cursor) = outlineRadius;
+ cursor += sizeof(outlineRadius);
- *((uint32_t*) cursor) = outlineAlpha;
+ *((uint32_t*)cursor) = outlineAlpha;
- *outLen = chunkLen;
- return buffer;
+ *outLen = chunkLen;
+ return buffer;
}
::std::ostream& operator<<(::std::ostream& out, const Range& range) {
- return out << "[" << range.start << ", " << range.end << ")";
+ return out << "[" << range.start << ", " << range.end << ")";
}
::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
- return out << "l=" << bounds.left
- << " t=" << bounds.top
- << " r=" << bounds.right
- << " b=" << bounds.bottom;
+ return out << "l=" << bounds.left << " t=" << bounds.top
+ << " r=" << bounds.right << " b=" << bounds.bottom;
}
::std::ostream& operator<<(::std::ostream& out, const NinePatch& ninePatch) {
- return out << "horizontalStretch:" << util::joiner(ninePatch.horizontalStretchRegions, " ")
- << " verticalStretch:" << util::joiner(ninePatch.verticalStretchRegions, " ")
- << " padding: " << ninePatch.padding
- << ", bounds: " << ninePatch.layoutBounds
- << ", outline: " << ninePatch.outline
- << " rad=" << ninePatch.outlineRadius
- << " alpha=" << ninePatch.outlineAlpha;
+ return out << "horizontalStretch:"
+ << util::joiner(ninePatch.horizontalStretchRegions, " ")
+ << " verticalStretch:"
+ << util::joiner(ninePatch.verticalStretchRegions, " ")
+ << " padding: " << ninePatch.padding
+ << ", bounds: " << ninePatch.layoutBounds
+ << ", outline: " << ninePatch.outline
+ << " rad=" << ninePatch.outlineRadius
+ << " alpha=" << ninePatch.outlineAlpha;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/NinePatch_test.cpp b/tools/aapt2/compile/NinePatch_test.cpp
index 3106ff8..b8eda09 100644
--- a/tools/aapt2/compile/NinePatch_test.cpp
+++ b/tools/aapt2/compile/NinePatch_test.cpp
@@ -21,8 +21,8 @@
// Pixels are in RGBA_8888 packing.
-#define RED "\xff\x00\x00\xff"
-#define BLUE "\x00\x00\xff\xff"
+#define RED "\xff\x00\x00\xff"
+#define BLUE "\x00\x00\xff\xff"
#define GREEN "\xff\x00\x00\xff"
#define GR_70 "\xff\x00\x00\xb3"
#define GR_50 "\xff\x00\x00\x80"
@@ -32,327 +32,346 @@
#define TRANS "\x00\x00\x00\x00"
static uint8_t* k2x2[] = {
- (uint8_t*) WHITE WHITE,
- (uint8_t*) WHITE WHITE,
+ (uint8_t*)WHITE WHITE, (uint8_t*)WHITE WHITE,
};
static uint8_t* kMixedNeutralColor3x3[] = {
- (uint8_t*) WHITE BLACK TRANS,
- (uint8_t*) TRANS RED TRANS,
- (uint8_t*) WHITE WHITE WHITE,
+ (uint8_t*)WHITE BLACK TRANS, (uint8_t*)TRANS RED TRANS,
+ (uint8_t*)WHITE WHITE WHITE,
};
static uint8_t* kTransparentNeutralColor3x3[] = {
- (uint8_t*) TRANS BLACK TRANS,
- (uint8_t*) BLACK RED BLACK,
- (uint8_t*) TRANS BLACK TRANS,
+ (uint8_t*)TRANS BLACK TRANS, (uint8_t*)BLACK RED BLACK,
+ (uint8_t*)TRANS BLACK TRANS,
};
static uint8_t* kSingleStretch7x6[] = {
- (uint8_t*) WHITE WHITE BLACK BLACK BLACK WHITE WHITE,
- (uint8_t*) WHITE RED RED RED RED RED WHITE,
- (uint8_t*) BLACK RED RED RED RED RED WHITE,
- (uint8_t*) BLACK RED RED RED RED RED WHITE,
- (uint8_t*) WHITE RED RED RED RED RED WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE BLACK BLACK BLACK WHITE WHITE,
+ (uint8_t*)WHITE RED RED RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED RED RED WHITE,
+ (uint8_t*)WHITE RED RED RED RED RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kMultipleStretch10x7[] = {
- (uint8_t*) WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE,
- (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
- (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
- (uint8_t*) WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
- (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
- (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kPadding6x5[] = {
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE BLACK,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE BLACK BLACK WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE BLACK BLACK WHITE WHITE,
};
static uint8_t* kLayoutBoundsWrongEdge3x3[] = {
- (uint8_t*) WHITE RED WHITE,
- (uint8_t*) RED WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE,
+ (uint8_t*)WHITE RED WHITE, (uint8_t*)RED WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE,
};
static uint8_t* kLayoutBoundsNotEdgeAligned5x5[] = {
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE RED WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE RED WHITE WHITE,
};
static uint8_t* kLayoutBounds5x5[] = {
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE RED WHITE RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE RED WHITE RED WHITE,
};
static uint8_t* kAsymmetricLayoutBounds5x5[] = {
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE RED WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE RED WHITE WHITE WHITE,
};
static uint8_t* kPaddingAndLayoutBounds5x5[] = {
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE WHITE WHITE WHITE BLACK,
- (uint8_t*) WHITE WHITE WHITE WHITE RED,
- (uint8_t*) WHITE RED BLACK RED WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE BLACK,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE RED BLACK RED WHITE,
};
static uint8_t* kColorfulImage5x5[] = {
- (uint8_t*) WHITE BLACK WHITE BLACK WHITE,
- (uint8_t*) BLACK RED BLUE GREEN WHITE,
- (uint8_t*) BLACK RED GREEN GREEN WHITE,
- (uint8_t*) WHITE TRANS BLUE GREEN WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE BLACK WHITE BLACK WHITE,
+ (uint8_t*)BLACK RED BLUE GREEN WHITE,
+ (uint8_t*)BLACK RED GREEN GREEN WHITE,
+ (uint8_t*)WHITE TRANS BLUE GREEN WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kOutlineOpaque10x10[] = {
- (uint8_t*) WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kOutlineTranslucent10x10[] = {
- (uint8_t*) WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
- (uint8_t*) WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kOutlineOffsetTranslucent12x10[] = {
- (uint8_t*) WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
- (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)
+ WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kOutlineRadius5x5[] = {
- (uint8_t*) WHITE BLACK BLACK BLACK WHITE,
- (uint8_t*) BLACK TRANS GREEN TRANS WHITE,
- (uint8_t*) BLACK GREEN GREEN GREEN WHITE,
- (uint8_t*) BLACK TRANS GREEN TRANS WHITE,
- (uint8_t*) WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE BLACK BLACK BLACK WHITE,
+ (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+ (uint8_t*)BLACK GREEN GREEN GREEN WHITE,
+ (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kStretchAndPadding5x5[] = {
- (uint8_t*) WHITE WHITE BLACK WHITE WHITE,
- (uint8_t*) WHITE RED RED RED WHITE,
- (uint8_t*) BLACK RED RED RED BLACK,
- (uint8_t*) WHITE RED RED RED WHITE,
- (uint8_t*) WHITE WHITE BLACK WHITE WHITE,
+ (uint8_t*)WHITE WHITE BLACK WHITE WHITE, (uint8_t*)WHITE RED RED RED WHITE,
+ (uint8_t*)BLACK RED RED RED BLACK, (uint8_t*)WHITE RED RED RED WHITE,
+ (uint8_t*)WHITE WHITE BLACK WHITE WHITE,
};
TEST(NinePatchTest, Minimum3x3) {
- std::string err;
- EXPECT_EQ(nullptr, NinePatch::create(k2x2, 2, 2, &err));
- EXPECT_FALSE(err.empty());
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::create(k2x2, 2, 2, &err));
+ EXPECT_FALSE(err.empty());
}
TEST(NinePatchTest, MixedNeutralColors) {
- std::string err;
- EXPECT_EQ(nullptr, NinePatch::create(kMixedNeutralColor3x3, 3, 3, &err));
- EXPECT_FALSE(err.empty());
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::create(kMixedNeutralColor3x3, 3, 3, &err));
+ EXPECT_FALSE(err.empty());
}
TEST(NinePatchTest, TransparentNeutralColor) {
- std::string err;
- EXPECT_NE(nullptr, NinePatch::create(kTransparentNeutralColor3x3, 3, 3, &err));
+ std::string err;
+ EXPECT_NE(nullptr,
+ NinePatch::create(kTransparentNeutralColor3x3, 3, 3, &err));
}
TEST(NinePatchTest, SingleStretchRegion) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kSingleStretch7x6, 7, 6, &err);
- ASSERT_NE(nullptr, ninePatch);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kSingleStretch7x6, 7, 6, &err);
+ ASSERT_NE(nullptr, ninePatch);
- ASSERT_EQ(1u, ninePatch->horizontalStretchRegions.size());
- ASSERT_EQ(1u, ninePatch->verticalStretchRegions.size());
+ ASSERT_EQ(1u, ninePatch->horizontalStretchRegions.size());
+ ASSERT_EQ(1u, ninePatch->verticalStretchRegions.size());
- EXPECT_EQ(Range(1, 4), ninePatch->horizontalStretchRegions.front());
- EXPECT_EQ(Range(1, 3), ninePatch->verticalStretchRegions.front());
+ EXPECT_EQ(Range(1, 4), ninePatch->horizontalStretchRegions.front());
+ EXPECT_EQ(Range(1, 3), ninePatch->verticalStretchRegions.front());
}
TEST(NinePatchTest, MultipleStretchRegions) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
- ASSERT_NE(nullptr, ninePatch);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
+ ASSERT_NE(nullptr, ninePatch);
- ASSERT_EQ(3u, ninePatch->horizontalStretchRegions.size());
- ASSERT_EQ(2u, ninePatch->verticalStretchRegions.size());
+ ASSERT_EQ(3u, ninePatch->horizontalStretchRegions.size());
+ ASSERT_EQ(2u, ninePatch->verticalStretchRegions.size());
- EXPECT_EQ(Range(1, 2), ninePatch->horizontalStretchRegions[0]);
- EXPECT_EQ(Range(3, 5), ninePatch->horizontalStretchRegions[1]);
- EXPECT_EQ(Range(6, 7), ninePatch->horizontalStretchRegions[2]);
+ EXPECT_EQ(Range(1, 2), ninePatch->horizontalStretchRegions[0]);
+ EXPECT_EQ(Range(3, 5), ninePatch->horizontalStretchRegions[1]);
+ EXPECT_EQ(Range(6, 7), ninePatch->horizontalStretchRegions[2]);
- EXPECT_EQ(Range(0, 2), ninePatch->verticalStretchRegions[0]);
- EXPECT_EQ(Range(3, 5), ninePatch->verticalStretchRegions[1]);
+ EXPECT_EQ(Range(0, 2), ninePatch->verticalStretchRegions[0]);
+ EXPECT_EQ(Range(3, 5), ninePatch->verticalStretchRegions[1]);
}
TEST(NinePatchTest, InferPaddingFromStretchRegions) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(1, 0, 1, 0), ninePatch->padding);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kMultipleStretch10x7, 10, 7, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(1, 0, 1, 0), ninePatch->padding);
}
TEST(NinePatchTest, Padding) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kPadding6x5, 6, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kPadding6x5, 6, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
}
TEST(NinePatchTest, LayoutBoundsAreOnWrongEdge) {
- std::string err;
- EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsWrongEdge3x3, 3, 3, &err));
- EXPECT_FALSE(err.empty());
+ std::string err;
+ EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsWrongEdge3x3, 3, 3, &err));
+ EXPECT_FALSE(err.empty());
}
TEST(NinePatchTest, LayoutBoundsMustTouchEdges) {
- std::string err;
- EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
- EXPECT_FALSE(err.empty());
+ std::string err;
+ EXPECT_EQ(nullptr,
+ NinePatch::create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
+ EXPECT_FALSE(err.empty());
}
TEST(NinePatchTest, LayoutBounds) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kLayoutBounds5x5, 5, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
- ninePatch = NinePatch::create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(1, 1, 0, 0), ninePatch->layoutBounds);
+ ninePatch = NinePatch::create(kAsymmetricLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(1, 1, 0, 0), ninePatch->layoutBounds);
}
TEST(NinePatchTest, PaddingAndLayoutBounds) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kPaddingAndLayoutBounds5x5, 5, 5,
- &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
- EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding);
+ EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds);
}
TEST(NinePatchTest, RegionColorsAreCorrect) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kColorfulImage5x5, 5, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kColorfulImage5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
- std::vector<uint32_t> expectedColors = {
- NinePatch::packRGBA((uint8_t*) RED),
- (uint32_t) android::Res_png_9patch::NO_COLOR,
- NinePatch::packRGBA((uint8_t*) GREEN),
- (uint32_t) android::Res_png_9patch::TRANSPARENT_COLOR,
- NinePatch::packRGBA((uint8_t*) BLUE),
- NinePatch::packRGBA((uint8_t*) GREEN),
- };
- EXPECT_EQ(expectedColors, ninePatch->regionColors);
+ std::vector<uint32_t> expectedColors = {
+ NinePatch::packRGBA((uint8_t*)RED),
+ (uint32_t)android::Res_png_9patch::NO_COLOR,
+ NinePatch::packRGBA((uint8_t*)GREEN),
+ (uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR,
+ NinePatch::packRGBA((uint8_t*)BLUE),
+ NinePatch::packRGBA((uint8_t*)GREEN),
+ };
+ EXPECT_EQ(expectedColors, ninePatch->regionColors);
}
TEST(NinePatchTest, OutlineFromOpaqueImage) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineOpaque10x10, 10, 10, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(2, 2, 2, 2), ninePatch->outline);
- EXPECT_EQ(0x000000ffu, ninePatch->outlineAlpha);
- EXPECT_EQ(0.0f, ninePatch->outlineRadius);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kOutlineOpaque10x10, 10, 10, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(2, 2, 2, 2), ninePatch->outline);
+ EXPECT_EQ(0x000000ffu, ninePatch->outlineAlpha);
+ EXPECT_EQ(0.0f, ninePatch->outlineRadius);
}
TEST(NinePatchTest, OutlineFromTranslucentImage) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineTranslucent10x10, 10, 10,
- &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(3, 3, 3, 3), ninePatch->outline);
- EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
- EXPECT_EQ(0.0f, ninePatch->outlineRadius);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kOutlineTranslucent10x10, 10, 10, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(3, 3, 3, 3), ninePatch->outline);
+ EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
+ EXPECT_EQ(0.0f, ninePatch->outlineRadius);
}
TEST(NinePatchTest, OutlineFromOffCenterImage) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineOffsetTranslucent12x10, 12, 10,
- &err);
- ASSERT_NE(nullptr, ninePatch);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kOutlineOffsetTranslucent12x10, 12, 10, &err);
+ ASSERT_NE(nullptr, ninePatch);
- // TODO(adamlesinski): The old AAPT algorithm searches from the outside to the middle
- // for each inset. If the outline is shifted, the search may not find a closer bounds.
- // This check should be:
- // EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline);
- // but until I know what behaviour I'm breaking, I will leave it at the incorrect:
- EXPECT_EQ(Bounds(4, 3, 3, 3), ninePatch->outline);
+ // TODO(adamlesinski): The old AAPT algorithm searches from the outside to the
+ // middle
+ // for each inset. If the outline is shifted, the search may not find a closer
+ // bounds.
+ // This check should be:
+ // EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline);
+ // but until I know what behaviour I'm breaking, I will leave it at the
+ // incorrect:
+ EXPECT_EQ(Bounds(4, 3, 3, 3), ninePatch->outline);
- EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
- EXPECT_EQ(0.0f, ninePatch->outlineRadius);
+ EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha);
+ EXPECT_EQ(0.0f, ninePatch->outlineRadius);
}
TEST(NinePatchTest, OutlineRadius) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineRadius5x5, 5, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
- EXPECT_EQ(Bounds(0, 0, 0, 0), ninePatch->outline);
- EXPECT_EQ(3.4142f, ninePatch->outlineRadius);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kOutlineRadius5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
+ EXPECT_EQ(Bounds(0, 0, 0, 0), ninePatch->outline);
+ EXPECT_EQ(3.4142f, ninePatch->outlineRadius);
}
::testing::AssertionResult bigEndianOne(uint8_t* cursor) {
- if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) {
- return ::testing::AssertionSuccess();
- }
- return ::testing::AssertionFailure() << "Not BigEndian 1";
+ if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) {
+ return ::testing::AssertionSuccess();
+ }
+ return ::testing::AssertionFailure() << "Not BigEndian 1";
}
TEST(NinePatchTest, SerializePngEndianness) {
- std::string err;
- std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kStretchAndPadding5x5, 5, 5, &err);
- ASSERT_NE(nullptr, ninePatch);
+ std::string err;
+ std::unique_ptr<NinePatch> ninePatch =
+ NinePatch::create(kStretchAndPadding5x5, 5, 5, &err);
+ ASSERT_NE(nullptr, ninePatch);
- size_t len;
- std::unique_ptr<uint8_t[]> data = ninePatch->serializeBase(&len);
- ASSERT_NE(nullptr, data);
- ASSERT_NE(0u, len);
+ size_t len;
+ std::unique_ptr<uint8_t[]> data = ninePatch->serializeBase(&len);
+ ASSERT_NE(nullptr, data);
+ ASSERT_NE(0u, len);
- // Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset + yDivsOffset
- // (12 bytes)
- uint8_t* cursor = data.get() + 12;
+ // Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset +
+ // yDivsOffset
+ // (12 bytes)
+ uint8_t* cursor = data.get() + 12;
- // Check that padding is big-endian. Expecting value 1.
- EXPECT_TRUE(bigEndianOne(cursor));
- EXPECT_TRUE(bigEndianOne(cursor + 4));
- EXPECT_TRUE(bigEndianOne(cursor + 8));
- EXPECT_TRUE(bigEndianOne(cursor + 12));
+ // Check that padding is big-endian. Expecting value 1.
+ EXPECT_TRUE(bigEndianOne(cursor));
+ EXPECT_TRUE(bigEndianOne(cursor + 4));
+ EXPECT_TRUE(bigEndianOne(cursor + 8));
+ EXPECT_TRUE(bigEndianOne(cursor + 12));
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index 055d8b5..9b5fa7e09 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-#include "util/BigBuffer.h"
#include "Png.h"
#include "Source.h"
+#include "util/BigBuffer.h"
#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
-#include <iostream>
#include <png.h>
+#include <zlib.h>
+#include <iostream>
#include <sstream>
#include <string>
#include <vector>
-#include <zlib.h>
namespace aapt {
@@ -33,158 +33,166 @@
constexpr size_t kPngSignatureSize = 8u;
struct PngInfo {
- ~PngInfo() {
- for (png_bytep row : rows) {
- if (row != nullptr) {
- delete[] row;
- }
- }
-
- delete[] xDivs;
- delete[] yDivs;
+ ~PngInfo() {
+ for (png_bytep row : rows) {
+ if (row != nullptr) {
+ delete[] row;
+ }
}
- void* serialize9Patch() {
- void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs,
- colors.data());
- reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
- return serialized;
- }
+ delete[] xDivs;
+ delete[] yDivs;
+ }
- uint32_t width = 0;
- uint32_t height = 0;
- std::vector<png_bytep> rows;
+ void* serialize9Patch() {
+ void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs,
+ yDivs, colors.data());
+ reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
+ return serialized;
+ }
- bool is9Patch = false;
- android::Res_png_9patch info9Patch;
- int32_t* xDivs = nullptr;
- int32_t* yDivs = nullptr;
- std::vector<uint32_t> colors;
+ uint32_t width = 0;
+ uint32_t height = 0;
+ std::vector<png_bytep> rows;
- // Layout padding.
- bool haveLayoutBounds = false;
- int32_t layoutBoundsLeft;
- int32_t layoutBoundsTop;
- int32_t layoutBoundsRight;
- int32_t layoutBoundsBottom;
+ bool is9Patch = false;
+ android::Res_png_9patch info9Patch;
+ int32_t* xDivs = nullptr;
+ int32_t* yDivs = nullptr;
+ std::vector<uint32_t> colors;
- // Round rect outline description.
- int32_t outlineInsetsLeft;
- int32_t outlineInsetsTop;
- int32_t outlineInsetsRight;
- int32_t outlineInsetsBottom;
- float outlineRadius;
- uint8_t outlineAlpha;
+ // Layout padding.
+ bool haveLayoutBounds = false;
+ int32_t layoutBoundsLeft;
+ int32_t layoutBoundsTop;
+ int32_t layoutBoundsRight;
+ int32_t layoutBoundsBottom;
+
+ // Round rect outline description.
+ int32_t outlineInsetsLeft;
+ int32_t outlineInsetsTop;
+ int32_t outlineInsetsRight;
+ int32_t outlineInsetsBottom;
+ float outlineRadius;
+ uint8_t outlineAlpha;
};
-static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
- std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
- if (!input->read(reinterpret_cast<char*>(data), length)) {
- png_error(readPtr, strerror(errno));
- }
+static void readDataFromStream(png_structp readPtr, png_bytep data,
+ png_size_t length) {
+ std::istream* input =
+ reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
+ if (!input->read(reinterpret_cast<char*>(data), length)) {
+ png_error(readPtr, strerror(errno));
+ }
}
-static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
- BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
- png_bytep buf = outBuffer->nextBlock<png_byte>(length);
- memcpy(buf, data, length);
+static void writeDataToStream(png_structp writePtr, png_bytep data,
+ png_size_t length) {
+ BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
+ png_bytep buf = outBuffer->nextBlock<png_byte>(length);
+ memcpy(buf, data, length);
}
-static void flushDataToStream(png_structp /*writePtr*/) {
-}
+static void flushDataToStream(png_structp /*writePtr*/) {}
static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
- IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
- diag->warn(DiagMessage() << warningMessage);
+ IDiagnostics* diag =
+ reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
+ diag->warn(DiagMessage() << warningMessage);
}
+static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr,
+ PngInfo* outInfo) {
+ if (setjmp(png_jmpbuf(readPtr))) {
+ diag->error(DiagMessage() << "failed reading png");
+ return false;
+ }
-static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
- if (setjmp(png_jmpbuf(readPtr))) {
- diag->error(DiagMessage() << "failed reading png");
- return false;
- }
+ png_set_sig_bytes(readPtr, kPngSignatureSize);
+ png_read_info(readPtr, infoPtr);
- png_set_sig_bytes(readPtr, kPngSignatureSize);
- png_read_info(readPtr, infoPtr);
+ int colorType, bitDepth, interlaceType, compressionType;
+ png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth,
+ &colorType, &interlaceType, &compressionType, nullptr);
- int colorType, bitDepth, interlaceType, compressionType;
- png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
- &interlaceType, &compressionType, nullptr);
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(readPtr);
+ }
- if (colorType == PNG_COLOR_TYPE_PALETTE) {
- png_set_palette_to_rgb(readPtr);
- }
+ if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+ png_set_expand_gray_1_2_4_to_8(readPtr);
+ }
- if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
- png_set_expand_gray_1_2_4_to_8(readPtr);
- }
+ if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(readPtr);
+ }
- if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
- png_set_tRNS_to_alpha(readPtr);
- }
+ if (bitDepth == 16) {
+ png_set_strip_16(readPtr);
+ }
- if (bitDepth == 16) {
- png_set_strip_16(readPtr);
- }
+ if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
+ png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
+ }
- if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
- png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
- }
+ if (colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(readPtr);
+ }
- if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
- png_set_gray_to_rgb(readPtr);
- }
+ png_set_interlace_handling(readPtr);
+ png_read_update_info(readPtr, infoPtr);
- png_set_interlace_handling(readPtr);
- png_read_update_info(readPtr, infoPtr);
+ const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
+ outInfo->rows.resize(outInfo->height);
+ for (size_t i = 0; i < outInfo->height; i++) {
+ outInfo->rows[i] = new png_byte[rowBytes];
+ }
- const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
- outInfo->rows.resize(outInfo->height);
- for (size_t i = 0; i < outInfo->height; i++) {
- outInfo->rows[i] = new png_byte[rowBytes];
- }
-
- png_read_image(readPtr, outInfo->rows.data());
- png_read_end(readPtr, infoPtr);
- return true;
+ png_read_image(readPtr, outInfo->rows.data());
+ png_read_end(readPtr, infoPtr);
+ return true;
}
-static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* data) {
- size_t patchSize = inPatch->serializedSize();
- void* newData = malloc(patchSize);
- memcpy(newData, data, patchSize);
- android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
- outPatch->fileToDevice();
- // deserialization is done in place, so outPatch == newData
- assert(outPatch == newData);
- assert(outPatch->numXDivs == inPatch->numXDivs);
- assert(outPatch->numYDivs == inPatch->numYDivs);
- assert(outPatch->paddingLeft == inPatch->paddingLeft);
- assert(outPatch->paddingRight == inPatch->paddingRight);
- assert(outPatch->paddingTop == inPatch->paddingTop);
- assert(outPatch->paddingBottom == inPatch->paddingBottom);
-/* for (int i = 0; i < outPatch->numXDivs; i++) {
- assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
- }
- for (int i = 0; i < outPatch->numYDivs; i++) {
- assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
- }
- for (int i = 0; i < outPatch->numColors; i++) {
- assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
- }*/
- free(newData);
+static void checkNinePatchSerialization(android::Res_png_9patch* inPatch,
+ void* data) {
+ size_t patchSize = inPatch->serializedSize();
+ void* newData = malloc(patchSize);
+ memcpy(newData, data, patchSize);
+ android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
+ outPatch->fileToDevice();
+ // deserialization is done in place, so outPatch == newData
+ assert(outPatch == newData);
+ assert(outPatch->numXDivs == inPatch->numXDivs);
+ assert(outPatch->numYDivs == inPatch->numYDivs);
+ assert(outPatch->paddingLeft == inPatch->paddingLeft);
+ assert(outPatch->paddingRight == inPatch->paddingRight);
+ assert(outPatch->paddingTop == inPatch->paddingTop);
+ assert(outPatch->paddingBottom == inPatch->paddingBottom);
+ /* for (int i = 0; i < outPatch->numXDivs; i++) {
+ assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numYDivs; i++) {
+ assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numColors; i++) {
+ assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
+ }*/
+ free(newData);
}
-/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) {
+/*static void dump_image(int w, int h, const png_byte* const* rows, int
+color_type) {
int i, j, rr, gg, bb, aa;
int bpp;
- if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
+ if (color_type == PNG_COLOR_TYPE_PALETTE || color_type ==
+PNG_COLOR_TYPE_GRAY) {
bpp = 1;
} else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
bpp = 2;
- } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+ } else if (color_type == PNG_COLOR_TYPE_RGB || color_type ==
+PNG_COLOR_TYPE_RGB_ALPHA) {
// We use a padding byte even when there is no alpha
bpp = 4;
} else {
@@ -224,1055 +232,1083 @@
}
}*/
-#define MAX(a,b) ((a)>(b)?(a):(b))
-#define ABS(a) ((a)<0?-(a):(a))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define ABS(a) ((a) < 0 ? -(a) : (a))
-static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
- png_colorp rgbPalette, png_bytep alphaPalette,
- int *paletteEntries, bool *hasTransparency, int *colorType,
+static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo,
+ int grayscaleTolerance, png_colorp rgbPalette,
+ png_bytep alphaPalette, int* paletteEntries,
+ bool* hasTransparency, int* colorType,
png_bytepp outRows) {
- int w = imageInfo.width;
- int h = imageInfo.height;
- int i, j, rr, gg, bb, aa, idx;
- uint32_t colors[256], col;
- int num_colors = 0;
- int maxGrayDeviation = 0;
+ int w = imageInfo.width;
+ int h = imageInfo.height;
+ int i, j, rr, gg, bb, aa, idx;
+ uint32_t colors[256], col;
+ int num_colors = 0;
+ int maxGrayDeviation = 0;
- bool isOpaque = true;
- bool isPalette = true;
- bool isGrayscale = true;
+ bool isOpaque = true;
+ bool isPalette = true;
+ bool isGrayscale = true;
- // Scan the entire image and determine if:
- // 1. Every pixel has R == G == B (grayscale)
- // 2. Every pixel has A == 255 (opaque)
- // 3. There are no more than 256 distinct RGBA colors
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors
- if (kDebug) {
- printf("Initial image data:\n");
- //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
- }
+ if (kDebug) {
+ printf("Initial image data:\n");
+ // dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
+ }
- for (j = 0; j < h; j++) {
- const png_byte* row = imageInfo.rows[j];
- png_bytep out = outRows[j];
- for (i = 0; i < w; i++) {
- rr = *row++;
- gg = *row++;
- bb = *row++;
- aa = *row++;
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
- int odev = maxGrayDeviation;
- maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
- maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
- maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
- if (maxGrayDeviation > odev) {
- if (kDebug) {
- printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
- maxGrayDeviation, i, j, rr, gg, bb, aa);
- }
- }
-
- // Check if image is really grayscale
- if (isGrayscale) {
- if (rr != gg || rr != bb) {
- if (kDebug) {
- printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
- i, j, rr, gg, bb, aa);
- }
- isGrayscale = false;
- }
- }
-
- // Check if image is really opaque
- if (isOpaque) {
- if (aa != 0xff) {
- if (kDebug) {
- printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
- i, j, rr, gg, bb, aa);
- }
- isOpaque = false;
- }
- }
-
- // Check if image is really <= 256 colors
- if (isPalette) {
- col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
- bool match = false;
- for (idx = 0; idx < num_colors; idx++) {
- if (colors[idx] == col) {
- match = true;
- break;
- }
- }
-
- // Write the palette index for the pixel to outRows optimistically
- // We might overwrite it later if we decide to encode as gray or
- // gray + alpha
- *out++ = idx;
- if (!match) {
- if (num_colors == 256) {
- if (kDebug) {
- printf("Found 257th color at %d, %d\n", i, j);
- }
- isPalette = false;
- } else {
- colors[num_colors++] = col;
- }
- }
- }
+ int odev = maxGrayDeviation;
+ maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+ if (maxGrayDeviation > odev) {
+ if (kDebug) {
+ printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+ maxGrayDeviation, i, j, rr, gg, bb, aa);
}
- }
+ }
- *paletteEntries = 0;
- *hasTransparency = !isOpaque;
- int bpp = isOpaque ? 3 : 4;
- int paletteSize = w * h + bpp * num_colors;
-
- if (kDebug) {
- printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
- printf("isOpaque = %s\n", isOpaque ? "true" : "false");
- printf("isPalette = %s\n", isPalette ? "true" : "false");
- printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
- paletteSize, 2 * w * h, bpp * w * h);
- printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
- }
-
- // Choose the best color type for the image.
- // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
- // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
- // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
- // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
- // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
- if (isGrayscale) {
- if (isOpaque) {
- *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
- } else {
- // Use a simple heuristic to determine whether using a palette will
- // save space versus using gray + alpha for each pixel.
- // This doesn't take into account chunk overhead, filtering, LZ
- // compression, etc.
- if (isPalette && (paletteSize < 2 * w * h)) {
- *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
- } else {
- *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
- }
+ // Check if image is really grayscale
+ if (isGrayscale) {
+ if (rr != gg || rr != bb) {
+ if (kDebug) {
+ printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", i, j,
+ rr, gg, bb, aa);
+ }
+ isGrayscale = false;
}
- } else if (isPalette && (paletteSize < bpp * w * h)) {
- *colorType = PNG_COLOR_TYPE_PALETTE;
+ }
+
+ // Check if image is really opaque
+ if (isOpaque) {
+ if (aa != 0xff) {
+ if (kDebug) {
+ printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", i, j,
+ rr, gg, bb, aa);
+ }
+ isOpaque = false;
+ }
+ }
+
+ // Check if image is really <= 256 colors
+ if (isPalette) {
+ col = (uint32_t)((rr << 24) | (gg << 16) | (bb << 8) | aa);
+ bool match = false;
+ for (idx = 0; idx < num_colors; idx++) {
+ if (colors[idx] == col) {
+ match = true;
+ break;
+ }
+ }
+
+ // Write the palette index for the pixel to outRows optimistically
+ // We might overwrite it later if we decide to encode as gray or
+ // gray + alpha
+ *out++ = idx;
+ if (!match) {
+ if (num_colors == 256) {
+ if (kDebug) {
+ printf("Found 257th color at %d, %d\n", i, j);
+ }
+ isPalette = false;
+ } else {
+ colors[num_colors++] = col;
+ }
+ }
+ }
+ }
+ }
+
+ *paletteEntries = 0;
+ *hasTransparency = !isOpaque;
+ int bpp = isOpaque ? 3 : 4;
+ int paletteSize = w * h + bpp * num_colors;
+
+ if (kDebug) {
+ printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
+ printf("isOpaque = %s\n", isOpaque ? "true" : "false");
+ printf("isPalette = %s\n", isPalette ? "true" : "false");
+ printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize,
+ 2 * w * h, bpp * w * h);
+ printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation,
+ grayscaleTolerance);
+ }
+
+ // Choose the best color type for the image.
+ // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+ // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct
+ // combinations
+ // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+ // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is
+ // sufficiently
+ // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+ if (isGrayscale) {
+ if (isOpaque) {
+ *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
} else {
- if (maxGrayDeviation <= grayscaleTolerance) {
- diag->note(DiagMessage()
- << "forcing image to gray (max deviation = "
- << maxGrayDeviation << ")");
- *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+ // Use a simple heuristic to determine whether using a palette will
+ // save space versus using gray + alpha for each pixel.
+ // This doesn't take into account chunk overhead, filtering, LZ
+ // compression, etc.
+ if (isPalette && (paletteSize < 2 * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
+ } else {
+ *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
+ }
+ }
+ } else if (isPalette && (paletteSize < bpp * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE;
+ } else {
+ if (maxGrayDeviation <= grayscaleTolerance) {
+ diag->note(DiagMessage() << "forcing image to gray (max deviation = "
+ << maxGrayDeviation << ")");
+ *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+ } else {
+ *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+ }
+
+ // Perform postprocessing of the image or palette data based on the final
+ // color type chosen
+
+ if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+ // Create separate RGB and Alpha palettes and set the number of colors
+ *paletteEntries = num_colors;
+
+ // Create the RGB and alpha palettes
+ for (int idx = 0; idx < num_colors; idx++) {
+ col = colors[idx];
+ rgbPalette[idx].red = (png_byte)((col >> 24) & 0xff);
+ rgbPalette[idx].green = (png_byte)((col >> 16) & 0xff);
+ rgbPalette[idx].blue = (png_byte)((col >> 8) & 0xff);
+ alphaPalette[idx] = (png_byte)(col & 0xff);
+ }
+ } else if (*colorType == PNG_COLOR_TYPE_GRAY ||
+ *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ // If the image is gray or gray + alpha, compact the pixels into outRows
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ if (isGrayscale) {
+ *out++ = rr;
} else {
- *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ *out++ = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
}
+ if (!isOpaque) {
+ *out++ = aa;
+ }
+ }
}
-
- // Perform postprocessing of the image or palette data based on the final
- // color type chosen
-
- if (*colorType == PNG_COLOR_TYPE_PALETTE) {
- // Create separate RGB and Alpha palettes and set the number of colors
- *paletteEntries = num_colors;
-
- // Create the RGB and alpha palettes
- for (int idx = 0; idx < num_colors; idx++) {
- col = colors[idx];
- rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
- rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
- rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
- alphaPalette[idx] = (png_byte) (col & 0xff);
- }
- } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
- // If the image is gray or gray + alpha, compact the pixels into outRows
- for (j = 0; j < h; j++) {
- const png_byte* row = imageInfo.rows[j];
- png_bytep out = outRows[j];
- for (i = 0; i < w; i++) {
- rr = *row++;
- gg = *row++;
- bb = *row++;
- aa = *row++;
-
- if (isGrayscale) {
- *out++ = rr;
- } else {
- *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
- }
- if (!isOpaque) {
- *out++ = aa;
- }
- }
- }
- }
+ }
}
-static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
- int grayScaleTolerance) {
- if (setjmp(png_jmpbuf(writePtr))) {
- diag->error(DiagMessage() << "failed to write png");
- return false;
+static bool writePng(IDiagnostics* diag, png_structp writePtr,
+ png_infop infoPtr, PngInfo* info, int grayScaleTolerance) {
+ if (setjmp(png_jmpbuf(writePtr))) {
+ diag->error(DiagMessage() << "failed to write png");
+ return false;
+ }
+
+ uint32_t width, height;
+ int colorType, bitDepth, interlaceType, compressionType;
+
+ png_unknown_chunk unknowns[3];
+ unknowns[0].data = nullptr;
+ unknowns[1].data = nullptr;
+ unknowns[2].data = nullptr;
+
+ png_bytepp outRows =
+ (png_bytepp)malloc((int)info->height * sizeof(png_bytep));
+ if (outRows == (png_bytepp)0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ for (uint32_t i = 0; i < info->height; i++) {
+ outRows[i] = (png_bytep)malloc(2 * (int)info->width);
+ if (outRows[i] == (png_bytep)0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
}
+ }
- uint32_t width, height;
- int colorType, bitDepth, interlaceType, compressionType;
+ png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
- png_unknown_chunk unknowns[3];
- unknowns[0].data = nullptr;
- unknowns[1].data = nullptr;
- unknowns[2].data = nullptr;
+ if (kDebug) {
+ diag->note(DiagMessage() << "writing image: w = " << info->width
+ << ", h = " << info->height);
+ }
- png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep));
- if (outRows == (png_bytepp) 0) {
- printf("Can't allocate output buffer!\n");
- exit(1);
- }
- for (uint32_t i = 0; i < info->height; i++) {
- outRows[i] = (png_bytep) malloc(2 * (int) info->width);
- if (outRows[i] == (png_bytep) 0) {
- printf("Can't allocate output buffer!\n");
- exit(1);
- }
- }
+ png_color rgbPalette[256];
+ png_byte alphaPalette[256];
+ bool hasTransparency;
+ int paletteEntries;
- png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+ analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
+ &paletteEntries, &hasTransparency, &colorType, outRows);
- if (kDebug) {
+ // If the image is a 9-patch, we need to preserve it as a ARGB file to make
+ // sure the pixels will not be pre-dithered/clamped until we decide they are
+ if (info->is9Patch &&
+ (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_PALETTE)) {
+ colorType = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+
+ if (kDebug) {
+ switch (colorType) {
+ case PNG_COLOR_TYPE_PALETTE:
+ diag->note(DiagMessage() << "has " << paletteEntries << " colors"
+ << (hasTransparency ? " (with alpha)" : "")
+ << ", using PNG_COLOR_TYPE_PALLETTE");
+ break;
+ case PNG_COLOR_TYPE_GRAY:
diag->note(DiagMessage()
- << "writing image: w = " << info->width
- << ", h = " << info->height);
+ << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ diag->note(DiagMessage()
+ << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ diag->note(DiagMessage()
+ << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
+ break;
}
+ }
- png_color rgbPalette[256];
- png_byte alphaPalette[256];
- bool hasTransparency;
- int paletteEntries;
+ png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
- analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
- &paletteEntries, &hasTransparency, &colorType, outRows);
-
- // If the image is a 9-patch, we need to preserve it as a ARGB file to make
- // sure the pixels will not be pre-dithered/clamped until we decide they are
- if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB ||
- colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) {
- colorType = PNG_COLOR_TYPE_RGB_ALPHA;
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
+ if (hasTransparency) {
+ png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries,
+ (png_color_16p)0);
}
+ png_set_filter(writePtr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+ }
+ if (info->is9Patch) {
+ int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
+ int pIndex = info->haveLayoutBounds ? 2 : 1;
+ int bIndex = 1;
+ int oIndex = 0;
+
+ // Chunks ordered thusly because older platforms depend on the base 9 patch
+ // data being last
+ png_bytep chunkNames = info->haveLayoutBounds
+ ? (png_bytep) "npOl\0npLb\0npTc\0"
+ : (png_bytep) "npOl\0npTc";
+
+ // base 9 patch data
if (kDebug) {
- switch (colorType) {
- case PNG_COLOR_TYPE_PALETTE:
- diag->note(DiagMessage()
- << "has " << paletteEntries
- << " colors" << (hasTransparency ? " (with alpha)" : "")
- << ", using PNG_COLOR_TYPE_PALLETTE");
- break;
- case PNG_COLOR_TYPE_GRAY:
- diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
- break;
- case PNG_COLOR_TYPE_GRAY_ALPHA:
- diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
- break;
- case PNG_COLOR_TYPE_RGB:
- diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
- break;
- case PNG_COLOR_TYPE_RGB_ALPHA:
- diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
- break;
- }
+ diag->note(DiagMessage() << "adding 9-patch info..");
+ }
+ strcpy((char*)unknowns[pIndex].name, "npTc");
+ unknowns[pIndex].data = (png_byte*)info->serialize9Patch();
+ unknowns[pIndex].size = info->info9Patch.serializedSize();
+ // TODO: remove the check below when everything works
+ checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
+
+ // automatically generated 9 patch outline data
+ int chunkSize = sizeof(png_uint_32) * 6;
+ strcpy((char*)unknowns[oIndex].name, "npOl");
+ unknowns[oIndex].data = (png_byte*)calloc(chunkSize, 1);
+ png_byte outputData[chunkSize];
+ memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
+ ((float*)outputData)[4] = info->outlineRadius;
+ ((png_uint_32*)outputData)[5] = info->outlineAlpha;
+ memcpy(unknowns[oIndex].data, &outputData, chunkSize);
+ unknowns[oIndex].size = chunkSize;
+
+ // optional optical inset / layout bounds data
+ if (info->haveLayoutBounds) {
+ int chunkSize = sizeof(png_uint_32) * 4;
+ strcpy((char*)unknowns[bIndex].name, "npLb");
+ unknowns[bIndex].data = (png_byte*)calloc(chunkSize, 1);
+ memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
+ unknowns[bIndex].size = chunkSize;
}
- png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
- PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
-
- if (colorType == PNG_COLOR_TYPE_PALETTE) {
- png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
- if (hasTransparency) {
- png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0);
- }
- png_set_filter(writePtr, 0, PNG_NO_FILTERS);
- } else {
- png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+ for (int i = 0; i < chunkCount; i++) {
+ unknowns[i].location = PNG_HAVE_PLTE;
}
-
- if (info->is9Patch) {
- int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
- int pIndex = info->haveLayoutBounds ? 2 : 1;
- int bIndex = 1;
- int oIndex = 0;
-
- // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
- png_bytep chunkNames = info->haveLayoutBounds
- ? (png_bytep)"npOl\0npLb\0npTc\0"
- : (png_bytep)"npOl\0npTc";
-
- // base 9 patch data
- if (kDebug) {
- diag->note(DiagMessage() << "adding 9-patch info..");
- }
- strcpy((char*)unknowns[pIndex].name, "npTc");
- unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
- unknowns[pIndex].size = info->info9Patch.serializedSize();
- // TODO: remove the check below when everything works
- checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
-
- // automatically generated 9 patch outline data
- int chunkSize = sizeof(png_uint_32) * 6;
- strcpy((char*)unknowns[oIndex].name, "npOl");
- unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1);
- png_byte outputData[chunkSize];
- memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
- ((float*) outputData)[4] = info->outlineRadius;
- ((png_uint_32*) outputData)[5] = info->outlineAlpha;
- memcpy(unknowns[oIndex].data, &outputData, chunkSize);
- unknowns[oIndex].size = chunkSize;
-
- // optional optical inset / layout bounds data
- if (info->haveLayoutBounds) {
- int chunkSize = sizeof(png_uint_32) * 4;
- strcpy((char*)unknowns[bIndex].name, "npLb");
- unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1);
- memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
- unknowns[bIndex].size = chunkSize;
- }
-
- for (int i = 0; i < chunkCount; i++) {
- unknowns[i].location = PNG_HAVE_PLTE;
- }
- png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS,
- chunkNames, chunkCount);
- png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
+ png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames,
+ chunkCount);
+ png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
#if PNG_LIBPNG_VER < 10600
- // Deal with unknown chunk location bug in 1.5.x and earlier.
- png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
- if (info->haveLayoutBounds) {
- png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
- }
+ // Deal with unknown chunk location bug in 1.5.x and earlier.
+ png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
+ if (info->haveLayoutBounds) {
+ png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
+ }
#endif
+ }
+
+ png_write_info(writePtr, infoPtr);
+
+ png_bytepp rows;
+ if (colorType == PNG_COLOR_TYPE_RGB ||
+ colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
+ if (colorType == PNG_COLOR_TYPE_RGB) {
+ png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
}
+ rows = info->rows.data();
+ } else {
+ rows = outRows;
+ }
+ png_write_image(writePtr, rows);
- png_write_info(writePtr, infoPtr);
+ if (kDebug) {
+ printf("Final image data:\n");
+ // dump_image(info->width, info->height, rows, colorType);
+ }
- png_bytepp rows;
- if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
- if (colorType == PNG_COLOR_TYPE_RGB) {
- png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
- }
- rows = info->rows.data();
- } else {
- rows = outRows;
- }
- png_write_image(writePtr, rows);
+ png_write_end(writePtr, infoPtr);
- if (kDebug) {
- printf("Final image data:\n");
- //dump_image(info->width, info->height, rows, colorType);
- }
+ for (uint32_t i = 0; i < info->height; i++) {
+ free(outRows[i]);
+ }
+ free(outRows);
+ free(unknowns[0].data);
+ free(unknowns[1].data);
+ free(unknowns[2].data);
- png_write_end(writePtr, infoPtr);
+ png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType,
+ &interlaceType, &compressionType, nullptr);
- for (uint32_t i = 0; i < info->height; i++) {
- free(outRows[i]);
- }
- free(outRows);
- free(unknowns[0].data);
- free(unknowns[1].data);
- free(unknowns[2].data);
-
- png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
- &compressionType, nullptr);
-
- if (kDebug) {
- diag->note(DiagMessage()
- << "image written: w = " << width << ", h = " << height
- << ", d = " << bitDepth << ", colors = " << colorType
- << ", inter = " << interlaceType << ", comp = " << compressionType);
- }
- return true;
+ if (kDebug) {
+ diag->note(DiagMessage() << "image written: w = " << width
+ << ", h = " << height << ", d = " << bitDepth
+ << ", colors = " << colorType
+ << ", inter = " << interlaceType
+ << ", comp = " << compressionType);
+ }
+ return true;
}
constexpr uint32_t kColorWhite = 0xffffffffu;
constexpr uint32_t kColorTick = 0xff000000u;
constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
-enum class TickType {
- kNone,
- kTick,
- kLayoutBounds,
- kBoth
-};
+enum class TickType { kNone, kTick, kLayoutBounds, kBoth };
static TickType tickType(png_bytep p, bool transparent, const char** outError) {
- png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+ png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
- if (transparent) {
- if (p[3] == 0) {
- return TickType::kNone;
- }
- if (color == kColorLayoutBoundsTick) {
- return TickType::kLayoutBounds;
- }
- if (color == kColorTick) {
- return TickType::kTick;
- }
-
- // Error cases
- if (p[3] != 0xff) {
- *outError = "Frame pixels must be either solid or transparent "
- "(not intermediate alphas)";
- return TickType::kNone;
- }
-
- if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
- *outError = "Ticks in transparent frame must be black or red";
- }
- return TickType::kTick;
- }
-
- if (p[3] != 0xFF) {
- *outError = "White frame must be a solid color (no alpha)";
- }
- if (color == kColorWhite) {
- return TickType::kNone;
- }
- if (color == kColorTick) {
- return TickType::kTick;
+ if (transparent) {
+ if (p[3] == 0) {
+ return TickType::kNone;
}
if (color == kColorLayoutBoundsTick) {
- return TickType::kLayoutBounds;
+ return TickType::kLayoutBounds;
+ }
+ if (color == kColorTick) {
+ return TickType::kTick;
+ }
+
+ // Error cases
+ if (p[3] != 0xff) {
+ *outError =
+ "Frame pixels must be either solid or transparent "
+ "(not intermediate alphas)";
+ return TickType::kNone;
}
if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
- *outError = "Ticks in white frame must be black or red";
- return TickType::kNone;
+ *outError = "Ticks in transparent frame must be black or red";
}
return TickType::kTick;
+ }
+
+ if (p[3] != 0xFF) {
+ *outError = "White frame must be a solid color (no alpha)";
+ }
+ if (color == kColorWhite) {
+ return TickType::kNone;
+ }
+ if (color == kColorTick) {
+ return TickType::kTick;
+ }
+ if (color == kColorLayoutBoundsTick) {
+ return TickType::kLayoutBounds;
+ }
+
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in white frame must be black or red";
+ return TickType::kNone;
+ }
+ return TickType::kTick;
}
-enum class TickState {
- kStart,
- kInside1,
- kOutside1
-};
+enum class TickState { kStart, kInside1, kOutside1 };
-static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
- int32_t* outLeft, int32_t* outRight, const char** outError,
+static bool getHorizontalTicks(png_bytep row, int width, bool transparent,
+ bool required, int32_t* outLeft,
+ int32_t* outRight, const char** outError,
uint8_t* outDivs, bool multipleAllowed) {
- *outLeft = *outRight = -1;
- TickState state = TickState::kStart;
- bool found = false;
+ *outLeft = *outRight = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
- for (int i = 1; i < width - 1; i++) {
- if (tickType(row+i*4, transparent, outError) == TickType::kTick) {
- if (state == TickState::kStart ||
- (state == TickState::kOutside1 && multipleAllowed)) {
- *outLeft = i-1;
- *outRight = width-2;
- found = true;
- if (outDivs != NULL) {
- *outDivs += 2;
- }
- state = TickState::kInside1;
- } else if (state == TickState::kOutside1) {
- *outError = "Can't have more than one marked region along edge";
- *outLeft = i;
- return false;
- }
- } else if (!*outError) {
- if (state == TickState::kInside1) {
- // We're done with this div. Move on to the next.
- *outRight = i-1;
- outRight += 2;
- outLeft += 2;
- state = TickState::kOutside1;
- }
- } else {
- *outLeft = i;
- return false;
+ for (int i = 1; i < width - 1; i++) {
+ if (tickType(row + i * 4, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
+ *outLeft = i - 1;
+ *outRight = width - 2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
}
- }
-
- if (required && !found) {
- *outError = "No marked region found along edge";
- *outLeft = -1;
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outLeft = i;
return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outRight = i - 1;
+ outRight += 2;
+ outLeft += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outLeft = i;
+ return false;
}
- return true;
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outLeft = -1;
+ return false;
+ }
+ return true;
}
-static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
- bool required, int32_t* outTop, int32_t* outBottom,
- const char** outError, uint8_t* outDivs, bool multipleAllowed) {
- *outTop = *outBottom = -1;
- TickState state = TickState::kStart;
- bool found = false;
+static bool getVerticalTicks(png_bytepp rows, int offset, int height,
+ bool transparent, bool required, int32_t* outTop,
+ int32_t* outBottom, const char** outError,
+ uint8_t* outDivs, bool multipleAllowed) {
+ *outTop = *outBottom = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
- for (int i = 1; i < height - 1; i++) {
- if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) {
- if (state == TickState::kStart ||
- (state == TickState::kOutside1 && multipleAllowed)) {
- *outTop = i-1;
- *outBottom = height-2;
- found = true;
- if (outDivs != NULL) {
- *outDivs += 2;
- }
- state = TickState::kInside1;
- } else if (state == TickState::kOutside1) {
- *outError = "Can't have more than one marked region along edge";
- *outTop = i;
- return false;
- }
- } else if (!*outError) {
- if (state == TickState::kInside1) {
- // We're done with this div. Move on to the next.
- *outBottom = i-1;
- outTop += 2;
- outBottom += 2;
- state = TickState::kOutside1;
- }
- } else {
- *outTop = i;
- return false;
+ for (int i = 1; i < height - 1; i++) {
+ if (tickType(rows[i] + offset, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
+ *outTop = i - 1;
+ *outBottom = height - 2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
}
- }
-
- if (required && !found) {
- *outError = "No marked region found along edge";
- *outTop = -1;
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outTop = i;
return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outBottom = i - 1;
+ outTop += 2;
+ outBottom += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outTop = i;
+ return false;
}
- return true;
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outTop = -1;
+ return false;
+ }
+ return true;
}
-static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
- bool /* required */, int32_t* outLeft,
- int32_t* outRight, const char** outError) {
- *outLeft = *outRight = 0;
+static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width,
+ bool transparent,
+ bool /* required */,
+ int32_t* outLeft, int32_t* outRight,
+ const char** outError) {
+ *outLeft = *outRight = 0;
- // Look for left tick
- if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
- // Starting with a layout padding tick
- int i = 1;
- while (i < width - 1) {
- (*outLeft)++;
- i++;
- if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
+ // Look for left tick
+ if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < width - 1) {
+ (*outLeft)++;
+ i++;
+ if (tickType(row + i * 4, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
}
+ }
- // Look for right tick
- if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
- // Ending with a layout padding tick
- int i = width - 2;
- while (i > 1) {
- (*outRight)++;
- i--;
- if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
+ // Look for right tick
+ if (tickType(row + (width - 2) * 4, transparent, outError) ==
+ TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = width - 2;
+ while (i > 1) {
+ (*outRight)++;
+ i--;
+ if (tickType(row + i * 4, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
}
- return true;
+ }
+ return true;
}
-static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
- bool /* required */, int32_t* outTop, int32_t* outBottom,
+static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset,
+ int height, bool transparent,
+ bool /* required */, int32_t* outTop,
+ int32_t* outBottom,
const char** outError) {
- *outTop = *outBottom = 0;
+ *outTop = *outBottom = 0;
- // Look for top tick
- if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
- // Starting with a layout padding tick
- int i = 1;
- while (i < height - 1) {
- (*outTop)++;
- i++;
- if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
+ // Look for top tick
+ if (tickType(rows[1] + offset, transparent, outError) ==
+ TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < height - 1) {
+ (*outTop)++;
+ i++;
+ if (tickType(rows[i] + offset, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
}
+ }
- // Look for bottom tick
- if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
- // Ending with a layout padding tick
- int i = height - 2;
- while (i > 1) {
- (*outBottom)++;
- i--;
- if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
- break;
- }
- }
+ // Look for bottom tick
+ if (tickType(rows[height - 2] + offset, transparent, outError) ==
+ TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = height - 2;
+ while (i > 1) {
+ (*outBottom)++;
+ i--;
+ if (tickType(rows[i] + offset, transparent, outError) !=
+ TickType::kLayoutBounds) {
+ break;
+ }
}
- return true;
+ }
+ return true;
}
-static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY,
- int dX, int dY, int* outInset) {
- uint8_t maxOpacity = 0;
- int inset = 0;
- *outInset = 0;
- for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
- png_byte* color = rows[y] + x * 4;
- uint8_t opacity = color[3];
- if (opacity > maxOpacity) {
- maxOpacity = opacity;
- *outInset = inset;
- }
- if (opacity == 0xff) return;
+static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX,
+ int endY, int dX, int dY, int* outInset) {
+ uint8_t maxOpacity = 0;
+ int inset = 0;
+ *outInset = 0;
+ for (int x = startX, y = startY; x != endX && y != endY;
+ x += dX, y += dY, inset++) {
+ png_byte* color = rows[y] + x * 4;
+ uint8_t opacity = color[3];
+ if (opacity > maxOpacity) {
+ maxOpacity = opacity;
+ *outInset = inset;
}
+ if (opacity == 0xff) return;
+ }
}
static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) {
- uint8_t maxAlpha = 0;
- for (int x = startX; x < endX; x++) {
- uint8_t alpha = (row + x * 4)[3];
- if (alpha > maxAlpha) maxAlpha = alpha;
- }
- return maxAlpha;
+ uint8_t maxAlpha = 0;
+ for (int x = startX; x < endX; x++) {
+ uint8_t alpha = (row + x * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
}
-static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
- uint8_t maxAlpha = 0;
- for (int y = startY; y < endY; y++) {
- uint8_t alpha = (rows[y] + offsetX * 4)[3];
- if (alpha > maxAlpha) maxAlpha = alpha;
- }
- return maxAlpha;
+static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY,
+ int endY) {
+ uint8_t maxAlpha = 0;
+ for (int y = startY; y < endY; y++) {
+ uint8_t alpha = (rows[y] + offsetX * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
}
static void getOutline(PngInfo* image) {
- int midX = image->width / 2;
- int midY = image->height / 2;
- int endX = image->width - 2;
- int endY = image->height - 2;
+ int midX = image->width / 2;
+ int midY = image->height / 2;
+ int endX = image->width - 2;
+ int endY = image->height - 2;
- // find left and right extent of nine patch content on center row
- if (image->width > 4) {
- findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
- findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
- &image->outlineInsetsRight);
- } else {
- image->outlineInsetsLeft = 0;
- image->outlineInsetsRight = 0;
- }
+ // find left and right extent of nine patch content on center row
+ if (image->width > 4) {
+ findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0,
+ &image->outlineInsetsLeft);
+ findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
+ &image->outlineInsetsRight);
+ } else {
+ image->outlineInsetsLeft = 0;
+ image->outlineInsetsRight = 0;
+ }
- // find top and bottom extent of nine patch content on center column
- if (image->height > 4) {
- findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
- findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
- &image->outlineInsetsBottom);
- } else {
- image->outlineInsetsTop = 0;
- image->outlineInsetsBottom = 0;
- }
+ // find top and bottom extent of nine patch content on center column
+ if (image->height > 4) {
+ findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1,
+ &image->outlineInsetsTop);
+ findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
+ &image->outlineInsetsBottom);
+ } else {
+ image->outlineInsetsTop = 0;
+ image->outlineInsetsBottom = 0;
+ }
- int innerStartX = 1 + image->outlineInsetsLeft;
- int innerStartY = 1 + image->outlineInsetsTop;
- int innerEndX = endX - image->outlineInsetsRight;
- int innerEndY = endY - image->outlineInsetsBottom;
- int innerMidX = (innerEndX + innerStartX) / 2;
- int innerMidY = (innerEndY + innerStartY) / 2;
+ int innerStartX = 1 + image->outlineInsetsLeft;
+ int innerStartY = 1 + image->outlineInsetsTop;
+ int innerEndX = endX - image->outlineInsetsRight;
+ int innerEndY = endY - image->outlineInsetsBottom;
+ int innerMidX = (innerEndX + innerStartX) / 2;
+ int innerMidY = (innerEndY + innerStartY) / 2;
- // assuming the image is a round rect, compute the radius by marching
- // diagonally from the top left corner towards the center
- image->outlineAlpha = std::max(
- maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
- maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
+ // assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center
+ image->outlineAlpha = std::max(
+ maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
+ maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
- int diagonalInset = 0;
- findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
- &diagonalInset);
+ int diagonalInset = 0;
+ findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX,
+ innerMidY, 1, 1, &diagonalInset);
- /* Determine source radius based upon inset:
- * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
- * sqrt(2) * r = sqrt(2) * i + r
- * (sqrt(2) - 1) * r = sqrt(2) * i
- * r = sqrt(2) / (sqrt(2) - 1) * i
- */
- image->outlineRadius = 3.4142f * diagonalInset;
+ /* Determine source radius based upon inset:
+ * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+ * sqrt(2) * r = sqrt(2) * i + r
+ * (sqrt(2) - 1) * r = sqrt(2) * i
+ * r = sqrt(2) / (sqrt(2) - 1) * i
+ */
+ image->outlineRadius = 3.4142f * diagonalInset;
- if (kDebug) {
- printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
- image->outlineInsetsLeft,
- image->outlineInsetsTop,
- image->outlineInsetsRight,
- image->outlineInsetsBottom,
- image->outlineRadius,
- image->outlineAlpha);
- }
+ if (kDebug) {
+ printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
+ image->outlineInsetsLeft, image->outlineInsetsTop,
+ image->outlineInsetsRight, image->outlineInsetsBottom,
+ image->outlineRadius, image->outlineAlpha);
+ }
}
-static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
- png_bytep color = rows[top] + left*4;
+static uint32_t getColor(png_bytepp rows, int left, int top, int right,
+ int bottom) {
+ png_bytep color = rows[top] + left * 4;
- if (left > right || top > bottom) {
- return android::Res_png_9patch::TRANSPARENT_COLOR;
- }
+ if (left > right || top > bottom) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
- while (top <= bottom) {
- for (int i = left; i <= right; i++) {
- png_bytep p = rows[top]+i*4;
- if (color[3] == 0) {
- if (p[3] != 0) {
- return android::Res_png_9patch::NO_COLOR;
- }
- } else if (p[0] != color[0] || p[1] != color[1] ||
- p[2] != color[2] || p[3] != color[3]) {
- return android::Res_png_9patch::NO_COLOR;
- }
+ while (top <= bottom) {
+ for (int i = left; i <= right; i++) {
+ png_bytep p = rows[top] + i * 4;
+ if (color[3] == 0) {
+ if (p[3] != 0) {
+ return android::Res_png_9patch::NO_COLOR;
}
- top++;
+ } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] ||
+ p[3] != color[3]) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
}
+ top++;
+ }
- if (color[3] == 0) {
- return android::Res_png_9patch::TRANSPARENT_COLOR;
- }
- return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
+ if (color[3] == 0) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return (color[3] << 24) | (color[0] << 16) | (color[1] << 8) | color[2];
}
static bool do9Patch(PngInfo* image, std::string* outError) {
- image->is9Patch = true;
+ image->is9Patch = true;
- int W = image->width;
- int H = image->height;
- int i, j;
+ int W = image->width;
+ int H = image->height;
+ int i, j;
- const int maxSizeXDivs = W * sizeof(int32_t);
- const int maxSizeYDivs = H * sizeof(int32_t);
- int32_t* xDivs = image->xDivs = new int32_t[W];
- int32_t* yDivs = image->yDivs = new int32_t[H];
- uint8_t numXDivs = 0;
- uint8_t numYDivs = 0;
+ const int maxSizeXDivs = W * sizeof(int32_t);
+ const int maxSizeYDivs = H * sizeof(int32_t);
+ int32_t* xDivs = image->xDivs = new int32_t[W];
+ int32_t* yDivs = image->yDivs = new int32_t[H];
+ uint8_t numXDivs = 0;
+ uint8_t numYDivs = 0;
- int8_t numColors;
- int numRows;
- int numCols;
- int top;
- int left;
- int right;
- int bottom;
- memset(xDivs, -1, maxSizeXDivs);
- memset(yDivs, -1, maxSizeYDivs);
- image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
- image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
- image->layoutBoundsLeft = image->layoutBoundsRight = 0;
- image->layoutBoundsTop = image->layoutBoundsBottom = 0;
+ int8_t numColors;
+ int numRows;
+ int numCols;
+ int top;
+ int left;
+ int right;
+ int bottom;
+ memset(xDivs, -1, maxSizeXDivs);
+ memset(yDivs, -1, maxSizeYDivs);
+ image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
+ image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
+ image->layoutBoundsLeft = image->layoutBoundsRight = 0;
+ image->layoutBoundsTop = image->layoutBoundsBottom = 0;
- png_bytep p = image->rows[0];
- bool transparent = p[3] == 0;
- bool hasColor = false;
+ png_bytep p = image->rows[0];
+ bool transparent = p[3] == 0;
+ bool hasColor = false;
- const char* errorMsg = nullptr;
- int errorPixel = -1;
- const char* errorEdge = nullptr;
+ const char* errorMsg = nullptr;
+ int errorPixel = -1;
+ const char* errorEdge = nullptr;
- int colorIndex = 0;
- std::vector<png_bytep> newRows;
+ int colorIndex = 0;
+ std::vector<png_bytep> newRows;
- // Validate size...
- if (W < 3 || H < 3) {
- errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
- goto getout;
+ // Validate size...
+ if (W < 3 || H < 3) {
+ errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
+ goto getout;
+ }
+
+ // Validate frame...
+ if (!transparent &&
+ (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+ errorMsg = "Must have one-pixel frame that is either transparent or white";
+ goto getout;
+ }
+
+ // Find left and right of sizing areas...
+ if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1],
+ &errorMsg, &numXDivs, true)) {
+ errorPixel = xDivs[0];
+ errorEdge = "top";
+ goto getout;
+ }
+
+ // Find top and bottom of sizing areas...
+ if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0],
+ &yDivs[1], &errorMsg, &numYDivs, true)) {
+ errorPixel = yDivs[0];
+ errorEdge = "left";
+ goto getout;
+ }
+
+ // Copy patch size data into image...
+ image->info9Patch.numXDivs = numXDivs;
+ image->info9Patch.numYDivs = numYDivs;
+
+ // Find left and right of padding area...
+ if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false,
+ &image->info9Patch.paddingLeft,
+ &image->info9Patch.paddingRight, &errorMsg, nullptr,
+ false)) {
+ errorPixel = image->info9Patch.paddingLeft;
+ errorEdge = "bottom";
+ goto getout;
+ }
+
+ // Find top and bottom of padding area...
+ if (!getVerticalTicks(image->rows.data(), (W - 1) * 4, H, transparent, false,
+ &image->info9Patch.paddingTop,
+ &image->info9Patch.paddingBottom, &errorMsg, nullptr,
+ false)) {
+ errorPixel = image->info9Patch.paddingTop;
+ errorEdge = "right";
+ goto getout;
+ }
+
+ // Find left and right of layout padding...
+ getHorizontalLayoutBoundsTicks(image->rows[H - 1], W, transparent, false,
+ &image->layoutBoundsLeft,
+ &image->layoutBoundsRight, &errorMsg);
+
+ getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent,
+ false, &image->layoutBoundsTop,
+ &image->layoutBoundsBottom, &errorMsg);
+
+ image->haveLayoutBounds =
+ image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 ||
+ image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0;
+
+ if (image->haveLayoutBounds) {
+ if (kDebug) {
+ printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft,
+ image->layoutBoundsTop, image->layoutBoundsRight,
+ image->layoutBoundsBottom);
}
+ }
- // Validate frame...
- if (!transparent &&
- (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
- errorMsg = "Must have one-pixel frame that is either transparent or white";
- goto getout;
- }
+ // use opacity of pixels to estimate the round rect outline
+ getOutline(image);
- // Find left and right of sizing areas...
- if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
- true)) {
- errorPixel = xDivs[0];
- errorEdge = "top";
- goto getout;
- }
+ // If padding is not yet specified, take values from size.
+ if (image->info9Patch.paddingLeft < 0) {
+ image->info9Patch.paddingLeft = xDivs[0];
+ image->info9Patch.paddingRight = W - 2 - xDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+ }
+ if (image->info9Patch.paddingTop < 0) {
+ image->info9Patch.paddingTop = yDivs[0];
+ image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
+ }
- // Find top and bottom of sizing areas...
- if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
- &errorMsg, &numYDivs, true)) {
- errorPixel = yDivs[0];
- errorEdge = "left";
- goto getout;
- }
+ /* if (kDebug) {
+ printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
+ xDivs[0], xDivs[1],
+ yDivs[0], yDivs[1]);
+ printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
+ image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
+ image->info9Patch.paddingTop,
+ image->info9Patch.paddingBottom);
+ }*/
- // Copy patch size data into image...
- image->info9Patch.numXDivs = numXDivs;
- image->info9Patch.numYDivs = numYDivs;
+ // Remove frame from image.
+ newRows.resize(H - 2);
+ for (i = 0; i < H - 2; i++) {
+ newRows[i] = image->rows[i + 1];
+ memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
+ }
+ image->rows.swap(newRows);
- // Find left and right of padding area...
- if (!getHorizontalTicks(image->rows[H-1], W, transparent, false,
- &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight,
- &errorMsg, nullptr, false)) {
- errorPixel = image->info9Patch.paddingLeft;
- errorEdge = "bottom";
- goto getout;
- }
+ image->width -= 2;
+ W = image->width;
+ image->height -= 2;
+ H = image->height;
- // Find top and bottom of padding area...
- if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false,
- &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom,
- &errorMsg, nullptr, false)) {
- errorPixel = image->info9Patch.paddingTop;
- errorEdge = "right";
- goto getout;
- }
+ // Figure out the number of rows and columns in the N-patch
+ numCols = numXDivs + 1;
+ if (xDivs[0] == 0) { // Column 1 is strechable
+ numCols--;
+ }
+ if (xDivs[numXDivs - 1] == W) {
+ numCols--;
+ }
+ numRows = numYDivs + 1;
+ if (yDivs[0] == 0) { // Row 1 is strechable
+ numRows--;
+ }
+ if (yDivs[numYDivs - 1] == H) {
+ numRows--;
+ }
- // Find left and right of layout padding...
- getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false,
- &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
+ // Make sure the amount of rows and columns will fit in the number of
+ // colors we can use in the 9-patch format.
+ if (numRows * numCols > 0x7F) {
+ errorMsg = "Too many rows and columns in 9-patch perimeter";
+ goto getout;
+ }
- getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false,
- &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
+ numColors = numRows * numCols;
+ image->info9Patch.numColors = numColors;
+ image->colors.resize(numColors);
- image->haveLayoutBounds = image->layoutBoundsLeft != 0
- || image->layoutBoundsRight != 0
- || image->layoutBoundsTop != 0
- || image->layoutBoundsBottom != 0;
+ // Fill in color information for each patch.
- if (image->haveLayoutBounds) {
- if (kDebug) {
- printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
- image->layoutBoundsRight, image->layoutBoundsBottom);
- }
- }
+ uint32_t c;
+ top = 0;
- // use opacity of pixels to estimate the round rect outline
- getOutline(image);
+ // The first row always starts with the top being at y=0 and the bottom
+ // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
+ // the first row is stretchable along the Y axis, otherwise it is fixed.
+ // The last row always ends with the bottom being bitmap.height and the top
+ // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+ // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+ // the Y axis, otherwise it is fixed.
+ //
+ // The first and last columns are similarly treated with respect to the X
+ // axis.
+ //
+ // The above is to help explain some of the special casing that goes on the
+ // code below.
- // If padding is not yet specified, take values from size.
- if (image->info9Patch.paddingLeft < 0) {
- image->info9Patch.paddingLeft = xDivs[0];
- image->info9Patch.paddingRight = W - 2 - xDivs[1];
+ // The initial yDiv and whether the first row is considered stretchable or
+ // not depends on whether yDiv[0] was zero or not.
+ for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
+ if (j == numYDivs) {
+ bottom = H;
} else {
- // Adjust value to be correct!
- image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+ bottom = yDivs[j];
}
- if (image->info9Patch.paddingTop < 0) {
- image->info9Patch.paddingTop = yDivs[0];
- image->info9Patch.paddingBottom = H - 2 - yDivs[1];
- } else {
- // Adjust value to be correct!
- image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
- }
-
-/* if (kDebug) {
- printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
- xDivs[0], xDivs[1],
- yDivs[0], yDivs[1]);
- printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
- image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
- image->info9Patch.paddingTop, image->info9Patch.paddingBottom);
- }*/
-
- // Remove frame from image.
- newRows.resize(H - 2);
- for (i = 0; i < H - 2; i++) {
- newRows[i] = image->rows[i + 1];
- memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
- }
- image->rows.swap(newRows);
-
- image->width -= 2;
- W = image->width;
- image->height -= 2;
- H = image->height;
-
- // Figure out the number of rows and columns in the N-patch
- numCols = numXDivs + 1;
- if (xDivs[0] == 0) { // Column 1 is strechable
- numCols--;
- }
- if (xDivs[numXDivs - 1] == W) {
- numCols--;
- }
- numRows = numYDivs + 1;
- if (yDivs[0] == 0) { // Row 1 is strechable
- numRows--;
- }
- if (yDivs[numYDivs - 1] == H) {
- numRows--;
- }
-
- // Make sure the amount of rows and columns will fit in the number of
- // colors we can use in the 9-patch format.
- if (numRows * numCols > 0x7F) {
- errorMsg = "Too many rows and columns in 9-patch perimeter";
- goto getout;
- }
-
- numColors = numRows * numCols;
- image->info9Patch.numColors = numColors;
- image->colors.resize(numColors);
-
- // Fill in color information for each patch.
-
- uint32_t c;
- top = 0;
-
- // The first row always starts with the top being at y=0 and the bottom
- // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
- // the first row is stretchable along the Y axis, otherwise it is fixed.
- // The last row always ends with the bottom being bitmap.height and the top
- // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
- // yDivs[numYDivs-1]. In the former case the last row is stretchable along
- // the Y axis, otherwise it is fixed.
- //
- // The first and last columns are similarly treated with respect to the X
- // axis.
- //
- // The above is to help explain some of the special casing that goes on the
- // code below.
-
- // The initial yDiv and whether the first row is considered stretchable or
- // not depends on whether yDiv[0] was zero or not.
- for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
- if (j == numYDivs) {
- bottom = H;
- } else {
- bottom = yDivs[j];
+ left = 0;
+ // The initial xDiv and whether the first column is considered
+ // stretchable or not depends on whether xDiv[0] was zero or not.
+ for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
+ if (i == numXDivs) {
+ right = W;
+ } else {
+ right = xDivs[i];
+ }
+ c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
+ image->colors[colorIndex++] = c;
+ if (kDebug) {
+ if (c != android::Res_png_9patch::NO_COLOR) {
+ hasColor = true;
}
- left = 0;
- // The initial xDiv and whether the first column is considered
- // stretchable or not depends on whether xDiv[0] was zero or not.
- for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
- if (i == numXDivs) {
- right = W;
- } else {
- right = xDivs[i];
- }
- c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
- image->colors[colorIndex++] = c;
- if (kDebug) {
- if (c != android::Res_png_9patch::NO_COLOR) {
- hasColor = true;
- }
- }
- left = right;
- }
- top = bottom;
+ }
+ left = right;
}
+ top = bottom;
+ }
- assert(colorIndex == numColors);
+ assert(colorIndex == numColors);
- if (kDebug && hasColor) {
- for (i = 0; i < numColors; i++) {
- if (i == 0) printf("Colors:\n");
- printf(" #%08x", image->colors[i]);
- if (i == numColors - 1) printf("\n");
- }
+ if (kDebug && hasColor) {
+ for (i = 0; i < numColors; i++) {
+ if (i == 0) printf("Colors:\n");
+ printf(" #%08x", image->colors[i]);
+ if (i == numColors - 1) printf("\n");
}
+ }
getout:
- if (errorMsg) {
- std::stringstream err;
- err << "9-patch malformed: " << errorMsg;
- if (errorEdge) {
- err << "." << std::endl;
- if (errorPixel >= 0) {
- err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
- } else {
- err << "Found along " << errorEdge << " edge";
- }
- }
- *outError = err.str();
- return false;
+ if (errorMsg) {
+ std::stringstream err;
+ err << "9-patch malformed: " << errorMsg;
+ if (errorEdge) {
+ err << "." << std::endl;
+ if (errorPixel >= 0) {
+ err << "Found at pixel #" << errorPixel << " along " << errorEdge
+ << " edge";
+ } else {
+ err << "Found along " << errorEdge << " edge";
+ }
}
- return true;
+ *outError = err.str();
+ return false;
+ }
+ return true;
}
+bool Png::process(const Source& source, std::istream* input,
+ BigBuffer* outBuffer, const PngOptions& options) {
+ png_byte signature[kPngSignatureSize];
-bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer,
- const PngOptions& options) {
- png_byte signature[kPngSignatureSize];
+ // Read the PNG signature first.
+ if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+ mDiag->error(DiagMessage() << strerror(errno));
+ return false;
+ }
- // Read the PNG signature first.
- if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
- mDiag->error(DiagMessage() << strerror(errno));
- return false;
+ // If the PNG signature doesn't match, bail early.
+ if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ mDiag->error(DiagMessage() << "not a valid png file");
+ return false;
+ }
+
+ bool result = false;
+ png_structp readPtr = nullptr;
+ png_infop infoPtr = nullptr;
+ png_structp writePtr = nullptr;
+ png_infop writeInfoPtr = nullptr;
+ PngInfo pngInfo = {};
+
+ readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!readPtr) {
+ mDiag->error(DiagMessage() << "failed to allocate read ptr");
+ goto bail;
+ }
+
+ infoPtr = png_create_info_struct(readPtr);
+ if (!infoPtr) {
+ mDiag->error(DiagMessage() << "failed to allocate info ptr");
+ goto bail;
+ }
+
+ png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr,
+ logWarning);
+
+ // Set the read function to read from std::istream.
+ png_set_read_fn(readPtr, (png_voidp)input, readDataFromStream);
+
+ if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
+ goto bail;
+ }
+
+ if (util::stringEndsWith(source.path, ".9.png")) {
+ std::string errorMsg;
+ if (!do9Patch(&pngInfo, &errorMsg)) {
+ mDiag->error(DiagMessage() << errorMsg);
+ goto bail;
}
+ }
- // If the PNG signature doesn't match, bail early.
- if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
- mDiag->error(DiagMessage() << "not a valid png file");
- return false;
- }
+ writePtr =
+ png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!writePtr) {
+ mDiag->error(DiagMessage() << "failed to allocate write ptr");
+ goto bail;
+ }
- bool result = false;
- png_structp readPtr = nullptr;
- png_infop infoPtr = nullptr;
- png_structp writePtr = nullptr;
- png_infop writeInfoPtr = nullptr;
- PngInfo pngInfo = {};
+ writeInfoPtr = png_create_info_struct(writePtr);
+ if (!writeInfoPtr) {
+ mDiag->error(DiagMessage() << "failed to allocate write info ptr");
+ goto bail;
+ }
- readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
- if (!readPtr) {
- mDiag->error(DiagMessage() << "failed to allocate read ptr");
- goto bail;
- }
+ png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
- infoPtr = png_create_info_struct(readPtr);
- if (!infoPtr) {
- mDiag->error(DiagMessage() << "failed to allocate info ptr");
- goto bail;
- }
+ // Set the write function to write to std::ostream.
+ png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream,
+ flushDataToStream);
- png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
+ if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo,
+ options.grayScaleTolerance)) {
+ goto bail;
+ }
- // Set the read function to read from std::istream.
- png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream);
-
- if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
- goto bail;
- }
-
- if (util::stringEndsWith(source.path, ".9.png")) {
- std::string errorMsg;
- if (!do9Patch(&pngInfo, &errorMsg)) {
- mDiag->error(DiagMessage() << errorMsg);
- goto bail;
- }
- }
-
- writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
- if (!writePtr) {
- mDiag->error(DiagMessage() << "failed to allocate write ptr");
- goto bail;
- }
-
- writeInfoPtr = png_create_info_struct(writePtr);
- if (!writeInfoPtr) {
- mDiag->error(DiagMessage() << "failed to allocate write info ptr");
- goto bail;
- }
-
- png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
-
- // Set the write function to write to std::ostream.
- png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
-
- if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) {
- goto bail;
- }
-
- result = true;
+ result = true;
bail:
- if (readPtr) {
- png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
- }
+ if (readPtr) {
+ png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
+ }
- if (writePtr) {
- png_destroy_write_struct(&writePtr, &writeInfoPtr);
- }
- return result;
+ if (writePtr) {
+ png_destroy_write_struct(&writePtr, &writeInfoPtr);
+ }
+ return result;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h
index 4a15d95..f9038af 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -31,51 +31,48 @@
namespace aapt {
struct PngOptions {
- int grayScaleTolerance = 0;
+ int grayScaleTolerance = 0;
};
class Png {
-public:
- explicit Png(IDiagnostics* diag) : mDiag(diag) {
- }
+ public:
+ explicit Png(IDiagnostics* diag) : mDiag(diag) {}
- bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
- const PngOptions& options);
+ bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+ const PngOptions& options);
-private:
- IDiagnostics* mDiag;
+ private:
+ IDiagnostics* mDiag;
- DISALLOW_COPY_AND_ASSIGN(Png);
+ DISALLOW_COPY_AND_ASSIGN(Png);
};
/**
* An InputStream that filters out unimportant PNG chunks.
*/
class PngChunkFilter : public io::InputStream {
-public:
- explicit PngChunkFilter(const StringPiece& data);
+ public:
+ explicit PngChunkFilter(const StringPiece& data);
- bool Next(const void** buffer, int* len) override;
- void BackUp(int count) override;
- bool Skip(int count) override;
+ bool Next(const void** buffer, int* len) override;
+ void BackUp(int count) override;
+ bool Skip(int count) override;
- int64_t ByteCount() const override {
- return static_cast<int64_t>(mWindowStart);
- }
+ int64_t ByteCount() const override {
+ return static_cast<int64_t>(mWindowStart);
+ }
- bool HadError() const override {
- return mError;
- }
+ bool HadError() const override { return mError; }
-private:
- bool consumeWindow(const void** buffer, int* len);
+ private:
+ bool consumeWindow(const void** buffer, int* len);
- StringPiece mData;
- size_t mWindowStart = 0;
- size_t mWindowEnd = 0;
- bool mError = false;
+ StringPiece mData;
+ size_t mWindowStart = 0;
+ size_t mWindowEnd = 0;
+ bool mError = false;
- DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
+ DISALLOW_COPY_AND_ASSIGN(PngChunkFilter);
};
/**
@@ -84,11 +81,13 @@
std::unique_ptr<Image> readPng(IAaptContext* context, io::InputStream* in);
/**
- * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream as a PNG.
+ * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream
+ * as a PNG.
*/
-bool writePng(IAaptContext* context, const Image* image, const NinePatch* ninePatch,
- io::OutputStream* out, const PngOptions& options);
+bool writePng(IAaptContext* context, const Image* image,
+ const NinePatch* ninePatch, io::OutputStream* out,
+ const PngOptions& options);
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_PNG_H
+#endif // AAPT_PNG_H
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index 70a881f..fb7fe92 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -25,149 +25,149 @@
// Useful helper function that encodes individual bytes into a uint32
// at compile time.
constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
- return (((uint32_t) a) << 24)
- | (((uint32_t) b) << 16)
- | (((uint32_t) c) << 8)
- | ((uint32_t) d);
+ return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) |
+ ((uint32_t)d);
}
// Whitelist of PNG chunk types that we want to keep in the resulting PNG.
enum PngChunkTypes {
- kPngChunkIHDR = u32(73, 72, 68, 82),
- kPngChunkIDAT = u32(73, 68, 65, 84),
- kPngChunkIEND = u32(73, 69, 78, 68),
- kPngChunkPLTE = u32(80, 76, 84, 69),
- kPngChunktRNS = u32(116, 82, 78, 83),
- kPngChunksRGB = u32(115, 82, 71, 66),
+ kPngChunkIHDR = u32(73, 72, 68, 82),
+ kPngChunkIDAT = u32(73, 68, 65, 84),
+ kPngChunkIEND = u32(73, 69, 78, 68),
+ kPngChunkPLTE = u32(80, 76, 84, 69),
+ kPngChunktRNS = u32(116, 82, 78, 83),
+ kPngChunksRGB = u32(115, 82, 71, 66),
};
static uint32_t peek32LE(const char* data) {
- uint32_t word = ((uint32_t) data[0]) & 0x000000ff;
- word <<= 8;
- word |= ((uint32_t) data[1]) & 0x000000ff;
- word <<= 8;
- word |= ((uint32_t) data[2]) & 0x000000ff;
- word <<= 8;
- word |= ((uint32_t) data[3]) & 0x000000ff;
- return word;
+ uint32_t word = ((uint32_t)data[0]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[1]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[2]) & 0x000000ff;
+ word <<= 8;
+ word |= ((uint32_t)data[3]) & 0x000000ff;
+ return word;
}
static bool isPngChunkWhitelisted(uint32_t type) {
- switch (type) {
+ switch (type) {
case kPngChunkIHDR:
case kPngChunkIDAT:
case kPngChunkIEND:
case kPngChunkPLTE:
case kPngChunktRNS:
case kPngChunksRGB:
- return true;
+ return true;
default:
- return false;
- }
+ return false;
+ }
}
PngChunkFilter::PngChunkFilter(const StringPiece& data) : mData(data) {
- if (util::stringStartsWith(mData, kPngSignature)) {
- mWindowStart = 0;
- mWindowEnd = strlen(kPngSignature);
- } else {
- mError = true;
- }
+ if (util::stringStartsWith(mData, kPngSignature)) {
+ mWindowStart = 0;
+ mWindowEnd = strlen(kPngSignature);
+ } else {
+ mError = true;
+ }
}
bool PngChunkFilter::consumeWindow(const void** buffer, int* len) {
- if (mWindowStart != mWindowEnd) {
- // We have bytes to give from our window.
- const int bytesRead = (int) (mWindowEnd - mWindowStart);
- *buffer = mData.data() + mWindowStart;
- *len = bytesRead;
- mWindowStart = mWindowEnd;
- return true;
- }
- return false;
+ if (mWindowStart != mWindowEnd) {
+ // We have bytes to give from our window.
+ const int bytesRead = (int)(mWindowEnd - mWindowStart);
+ *buffer = mData.data() + mWindowStart;
+ *len = bytesRead;
+ mWindowStart = mWindowEnd;
+ return true;
+ }
+ return false;
}
bool PngChunkFilter::Next(const void** buffer, int* len) {
- if (mError) {
- return false;
- }
-
- // In case BackUp was called, we must consume the window.
- if (consumeWindow(buffer, len)) {
- return true;
- }
-
- // Advance the window as far as possible (until we meet a chunk that
- // we want to strip).
- while (mWindowEnd < mData.size()) {
- // Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes.
- const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t);
-
- // Is there enough room for a chunk header?
- if (mData.size() - mWindowStart < kMinChunkHeaderSize) {
- mError = true;
- return false;
- }
-
- // Verify the chunk length.
- const uint32_t chunkLen = peek32LE(mData.data() + mWindowEnd);
- if (((uint64_t) chunkLen) + ((uint64_t) mWindowEnd) + sizeof(uint32_t) > mData.size()) {
- // Overflow.
- mError = true;
- return false;
- }
-
- // Do we strip this chunk?
- const uint32_t chunkType = peek32LE(mData.data() + mWindowEnd + sizeof(uint32_t));
- if (isPngChunkWhitelisted(chunkType)) {
- // Advance the window to include this chunk.
- mWindowEnd += kMinChunkHeaderSize + chunkLen;
- } else {
- // We want to strip this chunk. If we accumulated a window,
- // we must return the window now.
- if (mWindowStart != mWindowEnd) {
- break;
- }
-
- // The window is empty, so we can advance past this chunk
- // and keep looking for the next good chunk,
- mWindowEnd += kMinChunkHeaderSize + chunkLen;
- mWindowStart = mWindowEnd;
- }
- }
-
- if (consumeWindow(buffer, len)) {
- return true;
- }
+ if (mError) {
return false;
+ }
+
+ // In case BackUp was called, we must consume the window.
+ if (consumeWindow(buffer, len)) {
+ return true;
+ }
+
+ // Advance the window as far as possible (until we meet a chunk that
+ // we want to strip).
+ while (mWindowEnd < mData.size()) {
+ // Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes.
+ const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t);
+
+ // Is there enough room for a chunk header?
+ if (mData.size() - mWindowStart < kMinChunkHeaderSize) {
+ mError = true;
+ return false;
+ }
+
+ // Verify the chunk length.
+ const uint32_t chunkLen = peek32LE(mData.data() + mWindowEnd);
+ if (((uint64_t)chunkLen) + ((uint64_t)mWindowEnd) + sizeof(uint32_t) >
+ mData.size()) {
+ // Overflow.
+ mError = true;
+ return false;
+ }
+
+ // Do we strip this chunk?
+ const uint32_t chunkType =
+ peek32LE(mData.data() + mWindowEnd + sizeof(uint32_t));
+ if (isPngChunkWhitelisted(chunkType)) {
+ // Advance the window to include this chunk.
+ mWindowEnd += kMinChunkHeaderSize + chunkLen;
+ } else {
+ // We want to strip this chunk. If we accumulated a window,
+ // we must return the window now.
+ if (mWindowStart != mWindowEnd) {
+ break;
+ }
+
+ // The window is empty, so we can advance past this chunk
+ // and keep looking for the next good chunk,
+ mWindowEnd += kMinChunkHeaderSize + chunkLen;
+ mWindowStart = mWindowEnd;
+ }
+ }
+
+ if (consumeWindow(buffer, len)) {
+ return true;
+ }
+ return false;
}
void PngChunkFilter::BackUp(int count) {
- if (mError) {
- return;
- }
- mWindowStart -= count;
+ if (mError) {
+ return;
+ }
+ mWindowStart -= count;
}
bool PngChunkFilter::Skip(int count) {
- if (mError) {
- return false;
- }
+ if (mError) {
+ return false;
+ }
- const void* buffer;
- int len;
- while (count > 0) {
- if (!Next(&buffer, &len)) {
- return false;
- }
- if (len > count) {
- BackUp(len - count);
- count = 0;
- } else {
- count -= len;
- }
+ const void* buffer;
+ int len;
+ while (count > 0) {
+ if (!Next(&buffer, &len)) {
+ return false;
}
- return true;
+ if (len > count) {
+ BackUp(len - count);
+ count = 0;
+ } else {
+ count -= len;
+ }
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp
index a2e3f4f..4a74f7af7 100644
--- a/tools/aapt2/compile/PngCrunch.cpp
+++ b/tools/aapt2/compile/PngCrunch.cpp
@@ -16,13 +16,13 @@
#include "compile/Png.h"
-#include <algorithm>
#include <android-base/errors.h>
#include <android-base/macros.h>
#include <png.h>
+#include <zlib.h>
+#include <algorithm>
#include <unordered_map>
#include <unordered_set>
-#include <zlib.h>
namespace aapt {
@@ -33,250 +33,261 @@
* Custom deleter that destroys libpng read and info structs.
*/
class PngReadStructDeleter {
-public:
- explicit PngReadStructDeleter(png_structp readPtr, png_infop infoPtr) :
- mReadPtr(readPtr), mInfoPtr(infoPtr) {
- }
+ public:
+ explicit PngReadStructDeleter(png_structp readPtr, png_infop infoPtr)
+ : mReadPtr(readPtr), mInfoPtr(infoPtr) {}
- ~PngReadStructDeleter() {
- png_destroy_read_struct(&mReadPtr, &mInfoPtr, nullptr);
- }
+ ~PngReadStructDeleter() {
+ png_destroy_read_struct(&mReadPtr, &mInfoPtr, nullptr);
+ }
-private:
- png_structp mReadPtr;
- png_infop mInfoPtr;
+ private:
+ png_structp mReadPtr;
+ png_infop mInfoPtr;
- DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
+ DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
};
/**
* Custom deleter that destroys libpng write and info structs.
*/
class PngWriteStructDeleter {
-public:
- explicit PngWriteStructDeleter(png_structp writePtr, png_infop infoPtr) :
- mWritePtr(writePtr), mInfoPtr(infoPtr) {
- }
+ public:
+ explicit PngWriteStructDeleter(png_structp writePtr, png_infop infoPtr)
+ : mWritePtr(writePtr), mInfoPtr(infoPtr) {}
- ~PngWriteStructDeleter() {
- png_destroy_write_struct(&mWritePtr, &mInfoPtr);
- }
+ ~PngWriteStructDeleter() { png_destroy_write_struct(&mWritePtr, &mInfoPtr); }
-private:
- png_structp mWritePtr;
- png_infop mInfoPtr;
+ private:
+ png_structp mWritePtr;
+ png_infop mInfoPtr;
- DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
+ DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
};
// Custom warning logging method that uses IDiagnostics.
static void logWarning(png_structp pngPtr, png_const_charp warningMsg) {
- IDiagnostics* diag = (IDiagnostics*) png_get_error_ptr(pngPtr);
- diag->warn(DiagMessage() << warningMsg);
+ IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr);
+ diag->warn(DiagMessage() << warningMsg);
}
// Custom error logging method that uses IDiagnostics.
static void logError(png_structp pngPtr, png_const_charp errorMsg) {
- IDiagnostics* diag = (IDiagnostics*) png_get_error_ptr(pngPtr);
- diag->error(DiagMessage() << errorMsg);
+ IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr);
+ diag->error(DiagMessage() << errorMsg);
}
-static void readDataFromStream(png_structp pngPtr, png_bytep buffer, png_size_t len) {
- io::InputStream* in = (io::InputStream*) png_get_io_ptr(pngPtr);
+static void readDataFromStream(png_structp pngPtr, png_bytep buffer,
+ png_size_t len) {
+ io::InputStream* in = (io::InputStream*)png_get_io_ptr(pngPtr);
- const void* inBuffer;
- int inLen;
- if (!in->Next(&inBuffer, &inLen)) {
- if (in->HadError()) {
- std::string err = in->GetError();
- png_error(pngPtr, err.c_str());
- }
- return;
+ const void* inBuffer;
+ int inLen;
+ if (!in->Next(&inBuffer, &inLen)) {
+ if (in->HadError()) {
+ std::string err = in->GetError();
+ png_error(pngPtr, err.c_str());
}
+ return;
+ }
- const size_t bytesRead = std::min(static_cast<size_t>(inLen), len);
- memcpy(buffer, inBuffer, bytesRead);
- if (bytesRead != static_cast<size_t>(inLen)) {
- in->BackUp(inLen - static_cast<int>(bytesRead));
- }
+ const size_t bytesRead = std::min(static_cast<size_t>(inLen), len);
+ memcpy(buffer, inBuffer, bytesRead);
+ if (bytesRead != static_cast<size_t>(inLen)) {
+ in->BackUp(inLen - static_cast<int>(bytesRead));
+ }
}
-static void writeDataToStream(png_structp pngPtr, png_bytep buffer, png_size_t len) {
- io::OutputStream* out = (io::OutputStream*) png_get_io_ptr(pngPtr);
+static void writeDataToStream(png_structp pngPtr, png_bytep buffer,
+ png_size_t len) {
+ io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(pngPtr);
- void* outBuffer;
- int outLen;
- while (len > 0) {
- if (!out->Next(&outBuffer, &outLen)) {
- if (out->HadError()) {
- std::string err = out->GetError();
- png_error(pngPtr, err.c_str());
- }
- return;
- }
-
- const size_t bytesWritten = std::min(static_cast<size_t>(outLen), len);
- memcpy(outBuffer, buffer, bytesWritten);
-
- // Advance the input buffer.
- buffer += bytesWritten;
- len -= bytesWritten;
-
- // Advance the output buffer.
- outLen -= static_cast<int>(bytesWritten);
+ void* outBuffer;
+ int outLen;
+ while (len > 0) {
+ if (!out->Next(&outBuffer, &outLen)) {
+ if (out->HadError()) {
+ std::string err = out->GetError();
+ png_error(pngPtr, err.c_str());
+ }
+ return;
}
- // If the entire output buffer wasn't used, backup.
- if (outLen > 0) {
- out->BackUp(outLen);
- }
+ const size_t bytesWritten = std::min(static_cast<size_t>(outLen), len);
+ memcpy(outBuffer, buffer, bytesWritten);
+
+ // Advance the input buffer.
+ buffer += bytesWritten;
+ len -= bytesWritten;
+
+ // Advance the output buffer.
+ outLen -= static_cast<int>(bytesWritten);
+ }
+
+ // If the entire output buffer wasn't used, backup.
+ if (outLen > 0) {
+ out->BackUp(outLen);
+ }
}
std::unique_ptr<Image> readPng(IAaptContext* context, io::InputStream* in) {
- // Read the first 8 bytes of the file looking for the PNG signature.
- // Bail early if it does not match.
- const png_byte* signature;
- int bufferSize;
- if (!in->Next((const void**) &signature, &bufferSize)) {
- context->getDiagnostics()->error(DiagMessage()
- << android::base::SystemErrorCodeToString(errno));
- return {};
- }
+ // Read the first 8 bytes of the file looking for the PNG signature.
+ // Bail early if it does not match.
+ const png_byte* signature;
+ int bufferSize;
+ if (!in->Next((const void**)&signature, &bufferSize)) {
+ context->getDiagnostics()->error(
+ DiagMessage() << android::base::SystemErrorCodeToString(errno));
+ return {};
+ }
- if (static_cast<size_t>(bufferSize) < kPngSignatureSize
- || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
- context->getDiagnostics()->error(DiagMessage()
- << "file signature does not match PNG signature");
- return {};
- }
+ if (static_cast<size_t>(bufferSize) < kPngSignatureSize ||
+ png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "file signature does not match PNG signature");
+ return {};
+ }
- // Start at the beginning of the first chunk.
- in->BackUp(bufferSize - static_cast<int>(kPngSignatureSize));
+ // Start at the beginning of the first chunk.
+ in->BackUp(bufferSize - static_cast<int>(kPngSignatureSize));
- // Create and initialize the png_struct with the default error and warning handlers.
- // The header version is also passed in to ensure that this was built against the same
- // version of libpng.
- png_structp readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
- if (readPtr == nullptr) {
- context->getDiagnostics()->error(DiagMessage()
- << "failed to create libpng read png_struct");
- return {};
- }
+ // Create and initialize the png_struct with the default error and warning
+ // handlers.
+ // The header version is also passed in to ensure that this was built against
+ // the same
+ // version of libpng.
+ png_structp readPtr =
+ png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (readPtr == nullptr) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "failed to create libpng read png_struct");
+ return {};
+ }
- // Create and initialize the memory for image header and data.
- png_infop infoPtr = png_create_info_struct(readPtr);
- if (infoPtr == nullptr) {
- context->getDiagnostics()->error(DiagMessage() << "failed to create libpng read png_info");
- png_destroy_read_struct(&readPtr, nullptr, nullptr);
- return {};
- }
+ // Create and initialize the memory for image header and data.
+ png_infop infoPtr = png_create_info_struct(readPtr);
+ if (infoPtr == nullptr) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "failed to create libpng read png_info");
+ png_destroy_read_struct(&readPtr, nullptr, nullptr);
+ return {};
+ }
- // Automatically release PNG resources at end of scope.
- PngReadStructDeleter pngReadDeleter(readPtr, infoPtr);
+ // Automatically release PNG resources at end of scope.
+ PngReadStructDeleter pngReadDeleter(readPtr, infoPtr);
- // libpng uses longjmp to jump to an error handling routine.
- // setjmp will only return true if it was jumped to, aka there was
- // an error.
- if (setjmp(png_jmpbuf(readPtr))) {
- return {};
- }
+ // libpng uses longjmp to jump to an error handling routine.
+ // setjmp will only return true if it was jumped to, aka there was
+ // an error.
+ if (setjmp(png_jmpbuf(readPtr))) {
+ return {};
+ }
- // Handle warnings ourselves via IDiagnostics.
- png_set_error_fn(readPtr, (png_voidp) context->getDiagnostics(), logError, logWarning);
+ // Handle warnings ourselves via IDiagnostics.
+ png_set_error_fn(readPtr, (png_voidp)context->getDiagnostics(), logError,
+ logWarning);
- // Set up the read functions which read from our custom data sources.
- png_set_read_fn(readPtr, (png_voidp) in, readDataFromStream);
+ // Set up the read functions which read from our custom data sources.
+ png_set_read_fn(readPtr, (png_voidp)in, readDataFromStream);
- // Skip the signature that we already read.
- png_set_sig_bytes(readPtr, kPngSignatureSize);
+ // Skip the signature that we already read.
+ png_set_sig_bytes(readPtr, kPngSignatureSize);
- // Read the chunk headers.
- png_read_info(readPtr, infoPtr);
+ // Read the chunk headers.
+ png_read_info(readPtr, infoPtr);
- // Extract image meta-data from the various chunk headers.
- uint32_t width, height;
- int bitDepth, colorType, interlaceMethod, compressionMethod, filterMethod;
- png_get_IHDR(readPtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceMethod,
- &compressionMethod, &filterMethod);
+ // Extract image meta-data from the various chunk headers.
+ uint32_t width, height;
+ int bitDepth, colorType, interlaceMethod, compressionMethod, filterMethod;
+ png_get_IHDR(readPtr, infoPtr, &width, &height, &bitDepth, &colorType,
+ &interlaceMethod, &compressionMethod, &filterMethod);
- // When the image is read, expand it so that it is in RGBA 8888 format
- // so that image handling is uniform.
+ // When the image is read, expand it so that it is in RGBA 8888 format
+ // so that image handling is uniform.
- if (colorType == PNG_COLOR_TYPE_PALETTE) {
- png_set_palette_to_rgb(readPtr);
- }
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(readPtr);
+ }
- if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
- png_set_expand_gray_1_2_4_to_8(readPtr);
- }
+ if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+ png_set_expand_gray_1_2_4_to_8(readPtr);
+ }
- if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
- png_set_tRNS_to_alpha(readPtr);
- }
+ if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(readPtr);
+ }
- if (bitDepth == 16) {
- png_set_strip_16(readPtr);
- }
+ if (bitDepth == 16) {
+ png_set_strip_16(readPtr);
+ }
- if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
- png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
- }
+ if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
+ png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
+ }
- if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
- png_set_gray_to_rgb(readPtr);
- }
+ if (colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(readPtr);
+ }
- if (interlaceMethod != PNG_INTERLACE_NONE) {
- png_set_interlace_handling(readPtr);
- }
+ if (interlaceMethod != PNG_INTERLACE_NONE) {
+ png_set_interlace_handling(readPtr);
+ }
- // Once all the options for reading have been set, we need to flush
- // them to libpng.
- png_read_update_info(readPtr, infoPtr);
+ // Once all the options for reading have been set, we need to flush
+ // them to libpng.
+ png_read_update_info(readPtr, infoPtr);
- // 9-patch uses int32_t to index images, so we cap the image dimensions to something
- // that can always be represented by 9-patch.
- if (width > std::numeric_limits<int32_t>::max() ||
- height > std::numeric_limits<int32_t>::max()) {
- context->getDiagnostics()->error(DiagMessage() << "PNG image dimensions are too large: "
- << width << "x" << height);
- return {};
- }
+ // 9-patch uses int32_t to index images, so we cap the image dimensions to
+ // something
+ // that can always be represented by 9-patch.
+ if (width > std::numeric_limits<int32_t>::max() ||
+ height > std::numeric_limits<int32_t>::max()) {
+ context->getDiagnostics()->error(DiagMessage()
+ << "PNG image dimensions are too large: "
+ << width << "x" << height);
+ return {};
+ }
- std::unique_ptr<Image> outputImage = util::make_unique<Image>();
- outputImage->width = static_cast<int32_t>(width);
- outputImage->height = static_cast<int32_t>(height);
+ std::unique_ptr<Image> outputImage = util::make_unique<Image>();
+ outputImage->width = static_cast<int32_t>(width);
+ outputImage->height = static_cast<int32_t>(height);
- const size_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
- assert(rowBytes == 4 * width); // RGBA
+ const size_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
+ assert(rowBytes == 4 * width); // RGBA
- // Allocate one large block to hold the image.
- outputImage->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * rowBytes]);
+ // Allocate one large block to hold the image.
+ outputImage->data =
+ std::unique_ptr<uint8_t[]>(new uint8_t[height * rowBytes]);
- // Create an array of rows that index into the data block.
- outputImage->rows = std::unique_ptr<uint8_t*[]>(new uint8_t*[height]);
- for (uint32_t h = 0; h < height; h++) {
- outputImage->rows[h] = outputImage->data.get() + (h * rowBytes);
- }
+ // Create an array of rows that index into the data block.
+ outputImage->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
+ for (uint32_t h = 0; h < height; h++) {
+ outputImage->rows[h] = outputImage->data.get() + (h * rowBytes);
+ }
- // Actually read the image pixels.
- png_read_image(readPtr, outputImage->rows.get());
+ // Actually read the image pixels.
+ png_read_image(readPtr, outputImage->rows.get());
- // Finish reading. This will read any other chunks after the image data.
- png_read_end(readPtr, infoPtr);
+ // Finish reading. This will read any other chunks after the image data.
+ png_read_end(readPtr, infoPtr);
- return outputImage;
+ return outputImage;
}
/**
- * Experimentally chosen constant to be added to the overhead of using color type
- * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk.
- * Without this, many small PNGs encoded with palettes are larger after compression than
+ * Experimentally chosen constant to be added to the overhead of using color
+ * type
+ * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette
+ * chunk.
+ * Without this, many small PNGs encoded with palettes are larger after
+ * compression than
* the same PNGs encoded as RGBA.
*/
constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
-// Pick a color type by which to encode the image, based on which color type will take
+// Pick a color type by which to encode the image, based on which color type
+// will take
// the least amount of disk space.
//
// 9-patch images traditionally have not been encoded with palettes.
@@ -298,427 +309,450 @@
// - Grayscale + cheap alpha
// - Grayscale + alpha
//
-static int pickColorType(int32_t width, int32_t height,
- bool grayScale, bool convertibleToGrayScale, bool hasNinePatch,
+static int pickColorType(int32_t width, int32_t height, bool grayScale,
+ bool convertibleToGrayScale, bool hasNinePatch,
size_t colorPaletteSize, size_t alphaPaletteSize) {
- const size_t paletteChunkSize = 16 + colorPaletteSize * 3;
- const size_t alphaChunkSize = 16 + alphaPaletteSize;
- const size_t colorAlphaDataChunkSize = 16 + 4 * width * height;
- const size_t colorDataChunkSize = 16 + 3 * width * height;
- const size_t grayScaleAlphaDataChunkSize = 16 + 2 * width * height;
- const size_t paletteDataChunkSize = 16 + width * height;
+ const size_t paletteChunkSize = 16 + colorPaletteSize * 3;
+ const size_t alphaChunkSize = 16 + alphaPaletteSize;
+ const size_t colorAlphaDataChunkSize = 16 + 4 * width * height;
+ const size_t colorDataChunkSize = 16 + 3 * width * height;
+ const size_t grayScaleAlphaDataChunkSize = 16 + 2 * width * height;
+ const size_t paletteDataChunkSize = 16 + width * height;
- if (grayScale) {
- if (alphaPaletteSize == 0) {
- // This is the smallest the data can be.
- return PNG_COLOR_TYPE_GRAY;
- } else if (colorPaletteSize <= 256 && !hasNinePatch) {
- // This grayscale has alpha and can fit within a palette.
- // See if it is worth fitting into a palette.
- const size_t paletteThreshold = paletteChunkSize + alphaChunkSize +
- paletteDataChunkSize + kPaletteOverheadConstant;
- if (grayScaleAlphaDataChunkSize > paletteThreshold) {
- return PNG_COLOR_TYPE_PALETTE;
- }
- }
- return PNG_COLOR_TYPE_GRAY_ALPHA;
- }
-
-
- if (colorPaletteSize <= 256 && !hasNinePatch) {
- // This image can fit inside a palette. Let's see if it is worth it.
- size_t totalSizeWithPalette = paletteDataChunkSize + paletteChunkSize;
- size_t totalSizeWithoutPalette = colorDataChunkSize;
- if (alphaPaletteSize > 0) {
- totalSizeWithPalette += alphaPaletteSize;
- totalSizeWithoutPalette = colorAlphaDataChunkSize;
- }
-
- if (totalSizeWithoutPalette > totalSizeWithPalette + kPaletteOverheadConstant) {
- return PNG_COLOR_TYPE_PALETTE;
- }
- }
-
- if (convertibleToGrayScale) {
- if (alphaPaletteSize == 0) {
- return PNG_COLOR_TYPE_GRAY;
- } else {
- return PNG_COLOR_TYPE_GRAY_ALPHA;
- }
- }
-
+ if (grayScale) {
if (alphaPaletteSize == 0) {
- return PNG_COLOR_TYPE_RGB;
+ // This is the smallest the data can be.
+ return PNG_COLOR_TYPE_GRAY;
+ } else if (colorPaletteSize <= 256 && !hasNinePatch) {
+ // This grayscale has alpha and can fit within a palette.
+ // See if it is worth fitting into a palette.
+ const size_t paletteThreshold = paletteChunkSize + alphaChunkSize +
+ paletteDataChunkSize +
+ kPaletteOverheadConstant;
+ if (grayScaleAlphaDataChunkSize > paletteThreshold) {
+ return PNG_COLOR_TYPE_PALETTE;
+ }
}
- return PNG_COLOR_TYPE_RGBA;
+ return PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+
+ if (colorPaletteSize <= 256 && !hasNinePatch) {
+ // This image can fit inside a palette. Let's see if it is worth it.
+ size_t totalSizeWithPalette = paletteDataChunkSize + paletteChunkSize;
+ size_t totalSizeWithoutPalette = colorDataChunkSize;
+ if (alphaPaletteSize > 0) {
+ totalSizeWithPalette += alphaPaletteSize;
+ totalSizeWithoutPalette = colorAlphaDataChunkSize;
+ }
+
+ if (totalSizeWithoutPalette >
+ totalSizeWithPalette + kPaletteOverheadConstant) {
+ return PNG_COLOR_TYPE_PALETTE;
+ }
+ }
+
+ if (convertibleToGrayScale) {
+ if (alphaPaletteSize == 0) {
+ return PNG_COLOR_TYPE_GRAY;
+ } else {
+ return PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+ }
+
+ if (alphaPaletteSize == 0) {
+ return PNG_COLOR_TYPE_RGB;
+ }
+ return PNG_COLOR_TYPE_RGBA;
}
-// Assigns indices to the color and alpha palettes, encodes them, and then invokes
+// Assigns indices to the color and alpha palettes, encodes them, and then
+// invokes
// png_set_PLTE/png_set_tRNS.
// This must be done before writing image data.
-// Image data must be transformed to use the indices assigned within the palette.
+// Image data must be transformed to use the indices assigned within the
+// palette.
static void writePalette(png_structp writePtr, png_infop writeInfoPtr,
- std::unordered_map<uint32_t, int>* colorPalette,
- std::unordered_set<uint32_t>* alphaPalette) {
- assert(colorPalette->size() <= 256);
- assert(alphaPalette->size() <= 256);
+ std::unordered_map<uint32_t, int>* colorPalette,
+ std::unordered_set<uint32_t>* alphaPalette) {
+ assert(colorPalette->size() <= 256);
+ assert(alphaPalette->size() <= 256);
- // Populate the PNG palette struct and assign indices to the color
- // palette.
+ // Populate the PNG palette struct and assign indices to the color
+ // palette.
- // Colors in the alpha palette should have smaller indices.
- // This will ensure that we can truncate the alpha palette if it is
- // smaller than the color palette.
- int index = 0;
- for (uint32_t color : *alphaPalette) {
- (*colorPalette)[color] = index++;
+ // Colors in the alpha palette should have smaller indices.
+ // This will ensure that we can truncate the alpha palette if it is
+ // smaller than the color palette.
+ int index = 0;
+ for (uint32_t color : *alphaPalette) {
+ (*colorPalette)[color] = index++;
+ }
+
+ // Assign the rest of the entries.
+ for (auto& entry : *colorPalette) {
+ if (entry.second == -1) {
+ entry.second = index++;
}
+ }
- // Assign the rest of the entries.
- for (auto& entry : *colorPalette) {
- if (entry.second == -1) {
- entry.second = index++;
- }
+ // Create the PNG color palette struct.
+ auto colorPaletteBytes =
+ std::unique_ptr<png_color[]>(new png_color[colorPalette->size()]);
+
+ std::unique_ptr<png_byte[]> alphaPaletteBytes;
+ if (!alphaPalette->empty()) {
+ alphaPaletteBytes =
+ std::unique_ptr<png_byte[]>(new png_byte[alphaPalette->size()]);
+ }
+
+ for (const auto& entry : *colorPalette) {
+ const uint32_t color = entry.first;
+ const int index = entry.second;
+ assert(index >= 0);
+ assert(static_cast<size_t>(index) < colorPalette->size());
+
+ png_colorp slot = colorPaletteBytes.get() + index;
+ slot->red = color >> 24;
+ slot->green = color >> 16;
+ slot->blue = color >> 8;
+
+ const png_byte alpha = color & 0x000000ff;
+ if (alpha != 0xff && alphaPaletteBytes) {
+ assert(static_cast<size_t>(index) < alphaPalette->size());
+ alphaPaletteBytes[index] = alpha;
}
+ }
- // Create the PNG color palette struct.
- auto colorPaletteBytes = std::unique_ptr<png_color[]>(new png_color[colorPalette->size()]);
+ // The bytes get copied here, so it is safe to release colorPaletteBytes at
+ // the end of function
+ // scope.
+ png_set_PLTE(writePtr, writeInfoPtr, colorPaletteBytes.get(),
+ colorPalette->size());
- std::unique_ptr<png_byte[]> alphaPaletteBytes;
- if (!alphaPalette->empty()) {
- alphaPaletteBytes = std::unique_ptr<png_byte[]>(new png_byte[alphaPalette->size()]);
- }
-
- for (const auto& entry : *colorPalette) {
- const uint32_t color = entry.first;
- const int index = entry.second;
- assert(index >= 0);
- assert(static_cast<size_t>(index) < colorPalette->size());
-
- png_colorp slot = colorPaletteBytes.get() + index;
- slot->red = color >> 24;
- slot->green = color >> 16;
- slot->blue = color >> 8;
-
- const png_byte alpha = color & 0x000000ff;
- if (alpha != 0xff && alphaPaletteBytes) {
- assert(static_cast<size_t>(index) < alphaPalette->size());
- alphaPaletteBytes[index] = alpha;
- }
- }
-
- // The bytes get copied here, so it is safe to release colorPaletteBytes at the end of function
- // scope.
- png_set_PLTE(writePtr, writeInfoPtr, colorPaletteBytes.get(), colorPalette->size());
-
- if (alphaPaletteBytes) {
- png_set_tRNS(writePtr, writeInfoPtr, alphaPaletteBytes.get(), alphaPalette->size(),
- nullptr);
- }
+ if (alphaPaletteBytes) {
+ png_set_tRNS(writePtr, writeInfoPtr, alphaPaletteBytes.get(),
+ alphaPalette->size(), nullptr);
+ }
}
// Write the 9-patch custom PNG chunks to writeInfoPtr. This must be done before
// writing image data.
static void writeNinePatch(png_structp writePtr, png_infop writeInfoPtr,
const NinePatch* ninePatch) {
- // The order of the chunks is important.
- // 9-patch code in older platforms expects the 9-patch chunk to
- // be last.
+ // The order of the chunks is important.
+ // 9-patch code in older platforms expects the 9-patch chunk to
+ // be last.
- png_unknown_chunk unknownChunks[3];
- memset(unknownChunks, 0, sizeof(unknownChunks));
+ png_unknown_chunk unknownChunks[3];
+ memset(unknownChunks, 0, sizeof(unknownChunks));
- size_t index = 0;
- size_t chunkLen = 0;
+ size_t index = 0;
+ size_t chunkLen = 0;
- std::unique_ptr<uint8_t[]> serializedOutline =
- ninePatch->serializeRoundedRectOutline(&chunkLen);
- strcpy((char*) unknownChunks[index].name, "npOl");
+ std::unique_ptr<uint8_t[]> serializedOutline =
+ ninePatch->serializeRoundedRectOutline(&chunkLen);
+ strcpy((char*)unknownChunks[index].name, "npOl");
+ unknownChunks[index].size = chunkLen;
+ unknownChunks[index].data = (png_bytep)serializedOutline.get();
+ unknownChunks[index].location = PNG_HAVE_PLTE;
+ index++;
+
+ std::unique_ptr<uint8_t[]> serializedLayoutBounds;
+ if (ninePatch->layoutBounds.nonZero()) {
+ serializedLayoutBounds = ninePatch->serializeLayoutBounds(&chunkLen);
+ strcpy((char*)unknownChunks[index].name, "npLb");
unknownChunks[index].size = chunkLen;
- unknownChunks[index].data = (png_bytep) serializedOutline.get();
+ unknownChunks[index].data = (png_bytep)serializedLayoutBounds.get();
unknownChunks[index].location = PNG_HAVE_PLTE;
index++;
+ }
- std::unique_ptr<uint8_t[]> serializedLayoutBounds;
- if (ninePatch->layoutBounds.nonZero()) {
- serializedLayoutBounds = ninePatch->serializeLayoutBounds(&chunkLen);
- strcpy((char*) unknownChunks[index].name, "npLb");
- unknownChunks[index].size = chunkLen;
- unknownChunks[index].data = (png_bytep) serializedLayoutBounds.get();
- unknownChunks[index].location = PNG_HAVE_PLTE;
- index++;
- }
+ std::unique_ptr<uint8_t[]> serializedNinePatch =
+ ninePatch->serializeBase(&chunkLen);
+ strcpy((char*)unknownChunks[index].name, "npTc");
+ unknownChunks[index].size = chunkLen;
+ unknownChunks[index].data = (png_bytep)serializedNinePatch.get();
+ unknownChunks[index].location = PNG_HAVE_PLTE;
+ index++;
- std::unique_ptr<uint8_t[]> serializedNinePatch = ninePatch->serializeBase(&chunkLen);
- strcpy((char*) unknownChunks[index].name, "npTc");
- unknownChunks[index].size = chunkLen;
- unknownChunks[index].data = (png_bytep) serializedNinePatch.get();
- unknownChunks[index].location = PNG_HAVE_PLTE;
- index++;
+ // Handle all unknown chunks. We are manually setting the chunks here,
+ // so we will only ever handle our custom chunks.
+ png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
- // Handle all unknown chunks. We are manually setting the chunks here,
- // so we will only ever handle our custom chunks.
- png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
-
- // Set the actual chunks here. The data gets copied, so our buffers can
- // safely go out of scope.
- png_set_unknown_chunks(writePtr, writeInfoPtr, unknownChunks, index);
+ // Set the actual chunks here. The data gets copied, so our buffers can
+ // safely go out of scope.
+ png_set_unknown_chunks(writePtr, writeInfoPtr, unknownChunks, index);
}
-bool writePng(IAaptContext* context, const Image* image, const NinePatch* ninePatch,
- io::OutputStream* out, const PngOptions& options) {
- // Create and initialize the write png_struct with the default error and warning handlers.
- // The header version is also passed in to ensure that this was built against the same
- // version of libpng.
- png_structp writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
- nullptr, nullptr, nullptr);
- if (writePtr == nullptr) {
- context->getDiagnostics()->error(DiagMessage()
- << "failed to create libpng write png_struct");
- return false;
+bool writePng(IAaptContext* context, const Image* image,
+ const NinePatch* ninePatch, io::OutputStream* out,
+ const PngOptions& options) {
+ // Create and initialize the write png_struct with the default error and
+ // warning handlers.
+ // The header version is also passed in to ensure that this was built against
+ // the same
+ // version of libpng.
+ png_structp writePtr =
+ png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (writePtr == nullptr) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "failed to create libpng write png_struct");
+ return false;
+ }
+
+ // Allocate memory to store image header data.
+ png_infop writeInfoPtr = png_create_info_struct(writePtr);
+ if (writeInfoPtr == nullptr) {
+ context->getDiagnostics()->error(
+ DiagMessage() << "failed to create libpng write png_info");
+ png_destroy_write_struct(&writePtr, nullptr);
+ return false;
+ }
+
+ // Automatically release PNG resources at end of scope.
+ PngWriteStructDeleter pngWriteDeleter(writePtr, writeInfoPtr);
+
+ // libpng uses longjmp to jump to error handling routines.
+ // setjmp will return true only if it was jumped to, aka, there was an error.
+ if (setjmp(png_jmpbuf(writePtr))) {
+ return false;
+ }
+
+ // Handle warnings with our IDiagnostics.
+ png_set_error_fn(writePtr, (png_voidp)context->getDiagnostics(), logError,
+ logWarning);
+
+ // Set up the write functions which write to our custom data sources.
+ png_set_write_fn(writePtr, (png_voidp)out, writeDataToStream, nullptr);
+
+ // We want small files and can take the performance hit to achieve this goal.
+ png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+
+ // Begin analysis of the image data.
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors (palette).
+ std::unordered_map<uint32_t, int> colorPalette;
+ std::unordered_set<uint32_t> alphaPalette;
+ bool needsToZeroRGBChannelsOfTransparentPixels = false;
+ bool grayScale = true;
+ int maxGrayDeviation = 0;
+
+ for (int32_t y = 0; y < image->height; y++) {
+ const uint8_t* row = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int red = *row++;
+ int green = *row++;
+ int blue = *row++;
+ int alpha = *row++;
+
+ if (alpha == 0) {
+ // The color is completely transparent.
+ // For purposes of palettes and grayscale optimization,
+ // treat all channels as 0x00.
+ needsToZeroRGBChannelsOfTransparentPixels =
+ needsToZeroRGBChannelsOfTransparentPixels ||
+ (red != 0 || green != 0 || blue != 0);
+ red = green = blue = 0;
+ }
+
+ // Insert the color into the color palette.
+ const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
+ colorPalette[color] = -1;
+
+ // If the pixel has non-opaque alpha, insert it into the
+ // alpha palette.
+ if (alpha != 0xff) {
+ alphaPalette.insert(color);
+ }
+
+ // Check if the image is indeed grayscale.
+ if (grayScale) {
+ if (red != green || red != blue) {
+ grayScale = false;
+ }
+ }
+
+ // Calculate the gray scale deviation so that it can be compared
+ // with the threshold.
+ maxGrayDeviation = std::max(std::abs(red - green), maxGrayDeviation);
+ maxGrayDeviation = std::max(std::abs(green - blue), maxGrayDeviation);
+ maxGrayDeviation = std::max(std::abs(blue - red), maxGrayDeviation);
}
+ }
- // Allocate memory to store image header data.
- png_infop writeInfoPtr = png_create_info_struct(writePtr);
- if (writeInfoPtr == nullptr) {
- context->getDiagnostics()->error(DiagMessage() << "failed to create libpng write png_info");
- png_destroy_write_struct(&writePtr, nullptr);
- return false;
+ if (context->verbose()) {
+ DiagMessage msg;
+ msg << " paletteSize=" << colorPalette.size()
+ << " alphaPaletteSize=" << alphaPalette.size()
+ << " maxGrayDeviation=" << maxGrayDeviation
+ << " grayScale=" << (grayScale ? "true" : "false");
+ context->getDiagnostics()->note(msg);
+ }
+
+ const bool convertibleToGrayScale =
+ maxGrayDeviation <= options.grayScaleTolerance;
+
+ const int newColorType = pickColorType(
+ image->width, image->height, grayScale, convertibleToGrayScale,
+ ninePatch != nullptr, colorPalette.size(), alphaPalette.size());
+
+ if (context->verbose()) {
+ DiagMessage msg;
+ msg << "encoding PNG ";
+ if (ninePatch) {
+ msg << "(with 9-patch) as ";
}
-
- // Automatically release PNG resources at end of scope.
- PngWriteStructDeleter pngWriteDeleter(writePtr, writeInfoPtr);
-
- // libpng uses longjmp to jump to error handling routines.
- // setjmp will return true only if it was jumped to, aka, there was an error.
- if (setjmp(png_jmpbuf(writePtr))) {
- return false;
+ switch (newColorType) {
+ case PNG_COLOR_TYPE_GRAY:
+ msg << "GRAY";
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ msg << "GRAY + ALPHA";
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ msg << "RGB";
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ msg << "RGBA";
+ break;
+ case PNG_COLOR_TYPE_PALETTE:
+ msg << "PALETTE";
+ break;
+ default:
+ msg << "unknown type " << newColorType;
+ break;
}
+ context->getDiagnostics()->note(msg);
+ }
- // Handle warnings with our IDiagnostics.
- png_set_error_fn(writePtr, (png_voidp) context->getDiagnostics(), logError, logWarning);
+ png_set_IHDR(writePtr, writeInfoPtr, image->width, image->height, 8,
+ newColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
- // Set up the write functions which write to our custom data sources.
- png_set_write_fn(writePtr, (png_voidp) out, writeDataToStream, nullptr);
+ if (newColorType & PNG_COLOR_MASK_PALETTE) {
+ // Assigns indices to the palette, and writes the encoded palette to the
+ // libpng writePtr.
+ writePalette(writePtr, writeInfoPtr, &colorPalette, &alphaPalette);
+ png_set_filter(writePtr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+ }
- // We want small files and can take the performance hit to achieve this goal.
- png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+ if (ninePatch) {
+ writeNinePatch(writePtr, writeInfoPtr, ninePatch);
+ }
- // Begin analysis of the image data.
- // Scan the entire image and determine if:
- // 1. Every pixel has R == G == B (grayscale)
- // 2. Every pixel has A == 255 (opaque)
- // 3. There are no more than 256 distinct RGBA colors (palette).
- std::unordered_map<uint32_t, int> colorPalette;
- std::unordered_set<uint32_t> alphaPalette;
- bool needsToZeroRGBChannelsOfTransparentPixels = false;
- bool grayScale = true;
- int maxGrayDeviation = 0;
+ // Flush our updates to the header.
+ png_write_info(writePtr, writeInfoPtr);
+
+ // Write out each row of image data according to its encoding.
+ if (newColorType == PNG_COLOR_TYPE_PALETTE) {
+ // 1 byte/pixel.
+ auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
for (int32_t y = 0; y < image->height; y++) {
- const uint8_t* row = image->rows[y];
- for (int32_t x = 0; x < image->width; x++) {
- int red = *row++;
- int green = *row++;
- int blue = *row++;
- int alpha = *row++;
-
- if (alpha == 0) {
- // The color is completely transparent.
- // For purposes of palettes and grayscale optimization,
- // treat all channels as 0x00.
- needsToZeroRGBChannelsOfTransparentPixels =
- needsToZeroRGBChannelsOfTransparentPixels ||
- (red != 0 || green != 0 || blue != 0);
- red = green = blue = 0;
- }
-
- // Insert the color into the color palette.
- const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
- colorPalette[color] = -1;
-
- // If the pixel has non-opaque alpha, insert it into the
- // alpha palette.
- if (alpha != 0xff) {
- alphaPalette.insert(color);
- }
-
- // Check if the image is indeed grayscale.
- if (grayScale) {
- if (red != green || red != blue) {
- grayScale = false;
- }
- }
-
- // Calculate the gray scale deviation so that it can be compared
- // with the threshold.
- maxGrayDeviation = std::max(std::abs(red - green), maxGrayDeviation);
- maxGrayDeviation = std::max(std::abs(green - blue), maxGrayDeviation);
- maxGrayDeviation = std::max(std::abs(blue - red), maxGrayDeviation);
+ png_const_bytep inRow = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = *inRow++;
+ int gg = *inRow++;
+ int bb = *inRow++;
+ int aa = *inRow++;
+ if (aa == 0) {
+ // Zero out color channels when transparent.
+ rr = gg = bb = 0;
}
+
+ const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
+ const int idx = colorPalette[color];
+ assert(idx != -1);
+ outRow[x] = static_cast<png_byte>(idx);
+ }
+ png_write_row(writePtr, outRow.get());
}
+ } else if (newColorType == PNG_COLOR_TYPE_GRAY ||
+ newColorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ const size_t bpp = newColorType == PNG_COLOR_TYPE_GRAY ? 1 : 2;
+ auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
- if (context->verbose()) {
- DiagMessage msg;
- msg << " paletteSize=" << colorPalette.size()
- << " alphaPaletteSize=" << alphaPalette.size()
- << " maxGrayDeviation=" << maxGrayDeviation
- << " grayScale=" << (grayScale ? "true" : "false");
- context->getDiagnostics()->note(msg);
- }
-
- const bool convertibleToGrayScale = maxGrayDeviation <= options.grayScaleTolerance;
-
- const int newColorType = pickColorType(image->width, image->height, grayScale,
- convertibleToGrayScale, ninePatch != nullptr,
- colorPalette.size(), alphaPalette.size());
-
- if (context->verbose()) {
- DiagMessage msg;
- msg << "encoding PNG ";
- if (ninePatch) {
- msg << "(with 9-patch) as ";
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep inRow = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = inRow[x * 4];
+ int gg = inRow[x * 4 + 1];
+ int bb = inRow[x * 4 + 2];
+ int aa = inRow[x * 4 + 3];
+ if (aa == 0) {
+ // Zero out the gray channel when transparent.
+ rr = gg = bb = 0;
}
- switch (newColorType) {
- case PNG_COLOR_TYPE_GRAY:
- msg << "GRAY";
- break;
- case PNG_COLOR_TYPE_GRAY_ALPHA:
- msg << "GRAY + ALPHA";
- break;
- case PNG_COLOR_TYPE_RGB:
- msg << "RGB";
- break;
- case PNG_COLOR_TYPE_RGB_ALPHA:
- msg << "RGBA";
- break;
- case PNG_COLOR_TYPE_PALETTE:
- msg << "PALETTE";
- break;
- default:
- msg << "unknown type " << newColorType;
- break;
- }
- context->getDiagnostics()->note(msg);
- }
- png_set_IHDR(writePtr, writeInfoPtr, image->width, image->height, 8, newColorType,
- PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
-
- if (newColorType & PNG_COLOR_MASK_PALETTE) {
- // Assigns indices to the palette, and writes the encoded palette to the libpng writePtr.
- writePalette(writePtr, writeInfoPtr, &colorPalette, &alphaPalette);
- png_set_filter(writePtr, 0, PNG_NO_FILTERS);
- } else {
- png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
- }
-
- if (ninePatch) {
- writeNinePatch(writePtr, writeInfoPtr, ninePatch);
- }
-
- // Flush our updates to the header.
- png_write_info(writePtr, writeInfoPtr);
-
- // Write out each row of image data according to its encoding.
- if (newColorType == PNG_COLOR_TYPE_PALETTE) {
- // 1 byte/pixel.
- auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
-
- for (int32_t y = 0; y < image->height; y++) {
- png_const_bytep inRow = image->rows[y];
- for (int32_t x = 0; x < image->width; x++) {
- int rr = *inRow++;
- int gg = *inRow++;
- int bb = *inRow++;
- int aa = *inRow++;
- if (aa == 0) {
- // Zero out color channels when transparent.
- rr = gg = bb = 0;
- }
-
- const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
- const int idx = colorPalette[color];
- assert(idx != -1);
- outRow[x] = static_cast<png_byte>(idx);
- }
- png_write_row(writePtr, outRow.get());
- }
- } else if (newColorType == PNG_COLOR_TYPE_GRAY || newColorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
- const size_t bpp = newColorType == PNG_COLOR_TYPE_GRAY ? 1 : 2;
- auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
-
- for (int32_t y = 0; y < image->height; y++) {
- png_const_bytep inRow = image->rows[y];
- for (int32_t x = 0; x < image->width; x++) {
- int rr = inRow[x * 4];
- int gg = inRow[x * 4 + 1];
- int bb = inRow[x * 4 + 2];
- int aa = inRow[x * 4 + 3];
- if (aa == 0) {
- // Zero out the gray channel when transparent.
- rr = gg = bb = 0;
- }
-
- if (grayScale) {
- // The image was already grayscale, red == green == blue.
- outRow[x * bpp] = inRow[x * 4];
- } else {
- // The image is convertible to grayscale, use linear-luminance of
- // sRGB colorspace: https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
- outRow[x * bpp] = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
- }
-
- if (bpp == 2) {
- // Write out alpha if we have it.
- outRow[x * bpp + 1] = aa;
- }
- }
- png_write_row(writePtr, outRow.get());
- }
- } else if (newColorType == PNG_COLOR_TYPE_RGB || newColorType == PNG_COLOR_TYPE_RGBA) {
- const size_t bpp = newColorType == PNG_COLOR_TYPE_RGB ? 3 : 4;
- if (needsToZeroRGBChannelsOfTransparentPixels) {
- // The source RGBA data can't be used as-is, because we need to zero out the RGB
- // values of transparent pixels.
- auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
-
- for (int32_t y = 0; y < image->height; y++) {
- png_const_bytep inRow = image->rows[y];
- for (int32_t x = 0; x < image->width; x++) {
- int rr = *inRow++;
- int gg = *inRow++;
- int bb = *inRow++;
- int aa = *inRow++;
- if (aa == 0) {
- // Zero out the RGB channels when transparent.
- rr = gg = bb = 0;
- }
- outRow[x * bpp] = rr;
- outRow[x * bpp + 1] = gg;
- outRow[x * bpp + 2] = bb;
- if (bpp == 4) {
- outRow[x * bpp + 3] = aa;
- }
- }
- png_write_row(writePtr, outRow.get());
- }
+ if (grayScale) {
+ // The image was already grayscale, red == green == blue.
+ outRow[x * bpp] = inRow[x * 4];
} else {
- // The source image can be used as-is, just tell libpng whether or not to ignore
- // the alpha channel.
- if (newColorType == PNG_COLOR_TYPE_RGB) {
- // Delete the extraneous alpha values that we appended to our buffer
- // when reading the original values.
- png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
- }
- png_write_image(writePtr, image->rows.get());
+ // The image is convertible to grayscale, use linear-luminance of
+ // sRGB colorspace:
+ // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
+ outRow[x * bpp] =
+ (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
}
- } else {
- assert(false && "unreachable");
- }
- png_write_end(writePtr, writeInfoPtr);
- return true;
+ if (bpp == 2) {
+ // Write out alpha if we have it.
+ outRow[x * bpp + 1] = aa;
+ }
+ }
+ png_write_row(writePtr, outRow.get());
+ }
+ } else if (newColorType == PNG_COLOR_TYPE_RGB ||
+ newColorType == PNG_COLOR_TYPE_RGBA) {
+ const size_t bpp = newColorType == PNG_COLOR_TYPE_RGB ? 3 : 4;
+ if (needsToZeroRGBChannelsOfTransparentPixels) {
+ // The source RGBA data can't be used as-is, because we need to zero out
+ // the RGB
+ // values of transparent pixels.
+ auto outRow =
+ std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+
+ for (int32_t y = 0; y < image->height; y++) {
+ png_const_bytep inRow = image->rows[y];
+ for (int32_t x = 0; x < image->width; x++) {
+ int rr = *inRow++;
+ int gg = *inRow++;
+ int bb = *inRow++;
+ int aa = *inRow++;
+ if (aa == 0) {
+ // Zero out the RGB channels when transparent.
+ rr = gg = bb = 0;
+ }
+ outRow[x * bpp] = rr;
+ outRow[x * bpp + 1] = gg;
+ outRow[x * bpp + 2] = bb;
+ if (bpp == 4) {
+ outRow[x * bpp + 3] = aa;
+ }
+ }
+ png_write_row(writePtr, outRow.get());
+ }
+ } else {
+ // The source image can be used as-is, just tell libpng whether or not to
+ // ignore
+ // the alpha channel.
+ if (newColorType == PNG_COLOR_TYPE_RGB) {
+ // Delete the extraneous alpha values that we appended to our buffer
+ // when reading the original values.
+ png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
+ }
+ png_write_image(writePtr, image->rows.get());
+ }
+ } else {
+ assert(false && "unreachable");
+ }
+
+ png_write_end(writePtr, writeInfoPtr);
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 732101f..d8ed0bb 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -14,235 +14,242 @@
* limitations under the License.
*/
+#include "compile/PseudolocaleGenerator.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
-#include "compile/PseudolocaleGenerator.h"
#include "compile/Pseudolocalizer.h"
#include <algorithm>
namespace aapt {
-std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string,
- Pseudolocalizer::Method method,
- StringPool* pool) {
- Pseudolocalizer localizer(method);
+std::unique_ptr<StyledString> pseudolocalizeStyledString(
+ StyledString* string, Pseudolocalizer::Method method, StringPool* pool) {
+ Pseudolocalizer localizer(method);
- const StringPiece originalText = *string->value->str;
+ const StringPiece originalText = *string->value->str;
- StyleString localized;
+ StyleString localized;
- // Copy the spans. We will update their offsets when we localize.
- localized.spans.reserve(string->value->spans.size());
- for (const StringPool::Span& span : string->value->spans) {
- localized.spans.push_back(Span{ *span.name, span.firstChar, span.lastChar });
+ // Copy the spans. We will update their offsets when we localize.
+ localized.spans.reserve(string->value->spans.size());
+ for (const StringPool::Span& span : string->value->spans) {
+ localized.spans.push_back(Span{*span.name, span.firstChar, span.lastChar});
+ }
+
+ // The ranges are all represented with a single value. This is the start of
+ // one range and
+ // end of another.
+ struct Range {
+ size_t start;
+
+ // Once the new string is localized, these are the pointers to the spans to
+ // adjust.
+ // Since this struct represents the start of one range and end of another,
+ // we have
+ // the two pointers respectively.
+ uint32_t* updateStart;
+ uint32_t* updateEnd;
+ };
+
+ auto cmp = [](const Range& r, size_t index) -> bool {
+ return r.start < index;
+ };
+
+ // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7]
+ // The ranges are the spaces in between. In this example, with a total string
+ // length of 9,
+ // the vector represents: (0,1], (2,4], (5,6], (7,9]
+ //
+ std::vector<Range> ranges;
+ ranges.push_back(Range{0});
+ ranges.push_back(Range{originalText.size() - 1});
+ for (size_t i = 0; i < string->value->spans.size(); i++) {
+ const StringPool::Span& span = string->value->spans[i];
+
+ // Insert or update the Range marker for the start of this span.
+ auto iter =
+ std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp);
+ if (iter != ranges.end() && iter->start == span.firstChar) {
+ iter->updateStart = &localized.spans[i].firstChar;
+ } else {
+ ranges.insert(
+ iter, Range{span.firstChar, &localized.spans[i].firstChar, nullptr});
}
- // The ranges are all represented with a single value. This is the start of one range and
- // end of another.
- struct Range {
- size_t start;
+ // Insert or update the Range marker for the end of this span.
+ iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp);
+ if (iter != ranges.end() && iter->start == span.lastChar) {
+ iter->updateEnd = &localized.spans[i].lastChar;
+ } else {
+ ranges.insert(
+ iter, Range{span.lastChar, nullptr, &localized.spans[i].lastChar});
+ }
+ }
- // Once the new string is localized, these are the pointers to the spans to adjust.
- // Since this struct represents the start of one range and end of another, we have
- // the two pointers respectively.
- uint32_t* updateStart;
- uint32_t* updateEnd;
- };
+ localized.str += localizer.start();
- auto cmp = [](const Range& r, size_t index) -> bool {
- return r.start < index;
- };
-
- // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7]
- // The ranges are the spaces in between. In this example, with a total string length of 9,
- // the vector represents: (0,1], (2,4], (5,6], (7,9]
- //
- std::vector<Range> ranges;
- ranges.push_back(Range{ 0 });
- ranges.push_back(Range{ originalText.size() - 1 });
- for (size_t i = 0; i < string->value->spans.size(); i++) {
- const StringPool::Span& span = string->value->spans[i];
-
- // Insert or update the Range marker for the start of this span.
- auto iter = std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp);
- if (iter != ranges.end() && iter->start == span.firstChar) {
- iter->updateStart = &localized.spans[i].firstChar;
- } else {
- ranges.insert(iter,
- Range{ span.firstChar, &localized.spans[i].firstChar, nullptr });
- }
-
- // Insert or update the Range marker for the end of this span.
- iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp);
- if (iter != ranges.end() && iter->start == span.lastChar) {
- iter->updateEnd = &localized.spans[i].lastChar;
- } else {
- ranges.insert(iter,
- Range{ span.lastChar, nullptr, &localized.spans[i].lastChar });
- }
+ // Iterate over the ranges and localize each section.
+ for (size_t i = 0; i < ranges.size(); i++) {
+ const size_t start = ranges[i].start;
+ size_t len = originalText.size() - start;
+ if (i + 1 < ranges.size()) {
+ len = ranges[i + 1].start - start;
}
- localized.str += localizer.start();
-
- // Iterate over the ranges and localize each section.
- for (size_t i = 0; i < ranges.size(); i++) {
- const size_t start = ranges[i].start;
- size_t len = originalText.size() - start;
- if (i + 1 < ranges.size()) {
- len = ranges[i + 1].start - start;
- }
-
- if (ranges[i].updateStart) {
- *ranges[i].updateStart = localized.str.size();
- }
-
- if (ranges[i].updateEnd) {
- *ranges[i].updateEnd = localized.str.size();
- }
-
- localized.str += localizer.text(originalText.substr(start, len));
+ if (ranges[i].updateStart) {
+ *ranges[i].updateStart = localized.str.size();
}
- localized.str += localizer.end();
+ if (ranges[i].updateEnd) {
+ *ranges[i].updateEnd = localized.str.size();
+ }
- std::unique_ptr<StyledString> localizedString = util::make_unique<StyledString>(
- pool->makeRef(localized));
- localizedString->setSource(string->getSource());
- return localizedString;
+ localized.str += localizer.text(originalText.substr(start, len));
+ }
+
+ localized.str += localizer.end();
+
+ std::unique_ptr<StyledString> localizedString =
+ util::make_unique<StyledString>(pool->makeRef(localized));
+ localizedString->setSource(string->getSource());
+ return localizedString;
}
namespace {
struct Visitor : public RawValueVisitor {
- StringPool* mPool;
- Pseudolocalizer::Method mMethod;
- Pseudolocalizer mLocalizer;
+ StringPool* mPool;
+ Pseudolocalizer::Method mMethod;
+ Pseudolocalizer mLocalizer;
- // Either value or item will be populated upon visiting the value.
- std::unique_ptr<Value> mValue;
- std::unique_ptr<Item> mItem;
+ // Either value or item will be populated upon visiting the value.
+ std::unique_ptr<Value> mValue;
+ std::unique_ptr<Item> mItem;
- Visitor(StringPool* pool, Pseudolocalizer::Method method) :
- mPool(pool), mMethod(method), mLocalizer(method) {
- }
+ Visitor(StringPool* pool, Pseudolocalizer::Method method)
+ : mPool(pool), mMethod(method), mLocalizer(method) {}
- void visit(Plural* plural) override {
- std::unique_ptr<Plural> localized = util::make_unique<Plural>();
- for (size_t i = 0; i < plural->values.size(); i++) {
- Visitor subVisitor(mPool, mMethod);
- if (plural->values[i]) {
- plural->values[i]->accept(&subVisitor);
- if (subVisitor.mValue) {
- localized->values[i] = std::move(subVisitor.mItem);
- } else {
- localized->values[i] = std::unique_ptr<Item>(plural->values[i]->clone(mPool));
- }
- }
+ void visit(Plural* plural) override {
+ std::unique_ptr<Plural> localized = util::make_unique<Plural>();
+ for (size_t i = 0; i < plural->values.size(); i++) {
+ Visitor subVisitor(mPool, mMethod);
+ if (plural->values[i]) {
+ plural->values[i]->accept(&subVisitor);
+ if (subVisitor.mValue) {
+ localized->values[i] = std::move(subVisitor.mItem);
+ } else {
+ localized->values[i] =
+ std::unique_ptr<Item>(plural->values[i]->clone(mPool));
}
- localized->setSource(plural->getSource());
- localized->setWeak(true);
- mValue = std::move(localized);
+ }
}
+ localized->setSource(plural->getSource());
+ localized->setWeak(true);
+ mValue = std::move(localized);
+ }
- void visit(String* string) override {
- std::string result = mLocalizer.start() + mLocalizer.text(*string->value) +
- mLocalizer.end();
- std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result));
- localized->setSource(string->getSource());
- localized->setWeak(true);
- mItem = std::move(localized);
- }
+ void visit(String* string) override {
+ std::string result =
+ mLocalizer.start() + mLocalizer.text(*string->value) + mLocalizer.end();
+ std::unique_ptr<String> localized =
+ util::make_unique<String>(mPool->makeRef(result));
+ localized->setSource(string->getSource());
+ localized->setWeak(true);
+ mItem = std::move(localized);
+ }
- void visit(StyledString* string) override {
- mItem = pseudolocalizeStyledString(string, mMethod, mPool);
- mItem->setWeak(true);
- }
+ void visit(StyledString* string) override {
+ mItem = pseudolocalizeStyledString(string, mMethod, mPool);
+ mItem->setWeak(true);
+ }
};
ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base,
Pseudolocalizer::Method m) {
- ConfigDescription modified = base;
- switch (m) {
+ ConfigDescription modified = base;
+ switch (m) {
case Pseudolocalizer::Method::kAccent:
- modified.language[0] = 'e';
- modified.language[1] = 'n';
- modified.country[0] = 'X';
- modified.country[1] = 'A';
- break;
+ modified.language[0] = 'e';
+ modified.language[1] = 'n';
+ modified.country[0] = 'X';
+ modified.country[1] = 'A';
+ break;
case Pseudolocalizer::Method::kBidi:
- modified.language[0] = 'a';
- modified.language[1] = 'r';
- modified.country[0] = 'X';
- modified.country[1] = 'B';
- break;
+ modified.language[0] = 'a';
+ modified.language[1] = 'r';
+ modified.country[0] = 'X';
+ modified.country[1] = 'B';
+ break;
default:
- break;
- }
- return modified;
+ break;
+ }
+ return modified;
}
void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
ResourceConfigValue* originalValue,
- StringPool* pool,
- ResourceEntry* entry) {
- Visitor visitor(pool, method);
- originalValue->value->accept(&visitor);
+ StringPool* pool, ResourceEntry* entry) {
+ Visitor visitor(pool, method);
+ originalValue->value->accept(&visitor);
- std::unique_ptr<Value> localizedValue;
- if (visitor.mValue) {
- localizedValue = std::move(visitor.mValue);
- } else if (visitor.mItem) {
- localizedValue = std::move(visitor.mItem);
- }
+ std::unique_ptr<Value> localizedValue;
+ if (visitor.mValue) {
+ localizedValue = std::move(visitor.mValue);
+ } else if (visitor.mItem) {
+ localizedValue = std::move(visitor.mItem);
+ }
- if (!localizedValue) {
- return;
- }
+ if (!localizedValue) {
+ return;
+ }
- ConfigDescription configWithAccent = modifyConfigForPseudoLocale(
- originalValue->config, method);
+ ConfigDescription configWithAccent =
+ modifyConfigForPseudoLocale(originalValue->config, method);
- ResourceConfigValue* newConfigValue = entry->findOrCreateValue(
- configWithAccent, originalValue->product);
- if (!newConfigValue->value) {
- // Only use auto-generated pseudo-localization if none is defined.
- newConfigValue->value = std::move(localizedValue);
- }
+ ResourceConfigValue* newConfigValue =
+ entry->findOrCreateValue(configWithAccent, originalValue->product);
+ if (!newConfigValue->value) {
+ // Only use auto-generated pseudo-localization if none is defined.
+ newConfigValue->value = std::move(localizedValue);
+ }
}
/**
- * A value is pseudolocalizable if it does not define a locale (or is the default locale)
+ * A value is pseudolocalizable if it does not define a locale (or is the
+ * default locale)
* and is translateable.
*/
static bool isPseudolocalizable(ResourceConfigValue* configValue) {
- const int diff = configValue->config.diff(ConfigDescription::defaultConfig());
- if (diff & ConfigDescription::CONFIG_LOCALE) {
- return false;
- }
- return configValue->value->isTranslateable();
+ const int diff = configValue->config.diff(ConfigDescription::defaultConfig());
+ if (diff & ConfigDescription::CONFIG_LOCALE) {
+ return false;
+ }
+ return configValue->value->isTranslateable();
}
-} // namespace
+} // namespace
-bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) {
- for (auto& package : table->packages) {
- for (auto& type : package->types) {
- for (auto& entry : type->entries) {
- std::vector<ResourceConfigValue*> values = entry->findValuesIf(isPseudolocalizable);
+bool PseudolocaleGenerator::consume(IAaptContext* context,
+ ResourceTable* table) {
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ std::vector<ResourceConfigValue*> values =
+ entry->findValuesIf(isPseudolocalizable);
- for (ResourceConfigValue* value : values) {
- pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
- &table->stringPool, entry.get());
- pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
- &table->stringPool, entry.get());
- }
- }
+ for (ResourceConfigValue* value : values) {
+ pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
+ &table->stringPool, entry.get());
+ pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
+ &table->stringPool, entry.get());
}
+ }
}
- return true;
+ }
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h
index 4fbc516..4e97cb9 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.h
+++ b/tools/aapt2/compile/PseudolocaleGenerator.h
@@ -23,14 +23,13 @@
namespace aapt {
-std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string,
- Pseudolocalizer::Method method,
- StringPool* pool);
+std::unique_ptr<StyledString> pseudolocalizeStyledString(
+ StyledString* string, Pseudolocalizer::Method method, StringPool* pool);
struct PseudolocaleGenerator : public IResourceTableConsumer {
- bool consume(IAaptContext* context, ResourceTable* table) override;
+ bool consume(IAaptContext* context, ResourceTable* table) override;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H */
diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
index 1f62f90..64a3e97 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -23,99 +23,110 @@
namespace aapt {
TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) {
- StringPool pool;
- StyleString originalStyle;
- originalStyle.str = "Hello world!";
- originalStyle.spans = { Span{ "b", 2, 3 }, Span{ "b", 6, 7 }, Span{ "i", 1, 10 } };
+ StringPool pool;
+ StyleString originalStyle;
+ originalStyle.str = "Hello world!";
+ originalStyle.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}};
- std::unique_ptr<StyledString> newString = pseudolocalizeStyledString(
- util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
- Pseudolocalizer::Method::kNone, &pool);
+ std::unique_ptr<StyledString> newString = pseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
+ Pseudolocalizer::Method::kNone, &pool);
- EXPECT_EQ(originalStyle.str, *newString->value->str);
- ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
+ EXPECT_EQ(originalStyle.str, *newString->value->str);
+ ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
- EXPECT_EQ(std::string("He").size(), newString->value->spans[0].firstChar);
- EXPECT_EQ(std::string("Hel").size(), newString->value->spans[0].lastChar);
- EXPECT_EQ(std::string("b"), *newString->value->spans[0].name);
+ EXPECT_EQ(std::string("He").size(), newString->value->spans[0].firstChar);
+ EXPECT_EQ(std::string("Hel").size(), newString->value->spans[0].lastChar);
+ EXPECT_EQ(std::string("b"), *newString->value->spans[0].name);
- EXPECT_EQ(std::string("Hello ").size(), newString->value->spans[1].firstChar);
- EXPECT_EQ(std::string("Hello w").size(), newString->value->spans[1].lastChar);
- EXPECT_EQ(std::string("b"), *newString->value->spans[1].name);
+ EXPECT_EQ(std::string("Hello ").size(), newString->value->spans[1].firstChar);
+ EXPECT_EQ(std::string("Hello w").size(), newString->value->spans[1].lastChar);
+ EXPECT_EQ(std::string("b"), *newString->value->spans[1].name);
- EXPECT_EQ(std::string("H").size(), newString->value->spans[2].firstChar);
- EXPECT_EQ(std::string("Hello worl").size(), newString->value->spans[2].lastChar);
- EXPECT_EQ(std::string("i"), *newString->value->spans[2].name);
+ EXPECT_EQ(std::string("H").size(), newString->value->spans[2].firstChar);
+ EXPECT_EQ(std::string("Hello worl").size(),
+ newString->value->spans[2].lastChar);
+ EXPECT_EQ(std::string("i"), *newString->value->spans[2].name);
- originalStyle.spans.push_back(Span{ "em", 0, 11u });
+ originalStyle.spans.push_back(Span{"em", 0, 11u});
- newString = pseudolocalizeStyledString(
- util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
- Pseudolocalizer::Method::kAccent, &pool);
+ newString = pseudolocalizeStyledString(
+ util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(),
+ Pseudolocalizer::Method::kAccent, &pool);
- EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *newString->value->str);
- ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
+ EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *newString->value->str);
+ ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size());
- EXPECT_EQ(std::string("[Ĥé").size(), newString->value->spans[0].firstChar);
- EXPECT_EQ(std::string("[Ĥéļ").size(), newString->value->spans[0].lastChar);
+ EXPECT_EQ(std::string("[Ĥé").size(), newString->value->spans[0].firstChar);
+ EXPECT_EQ(std::string("[Ĥéļ").size(), newString->value->spans[0].lastChar);
- EXPECT_EQ(std::string("[Ĥéļļö ").size(), newString->value->spans[1].firstChar);
- EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(), newString->value->spans[1].lastChar);
+ EXPECT_EQ(std::string("[Ĥéļļö ").size(),
+ newString->value->spans[1].firstChar);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(),
+ newString->value->spans[1].lastChar);
- EXPECT_EQ(std::string("[Ĥ").size(), newString->value->spans[2].firstChar);
- EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(), newString->value->spans[2].lastChar);
+ EXPECT_EQ(std::string("[Ĥ").size(), newString->value->spans[2].firstChar);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(),
+ newString->value->spans[2].lastChar);
- EXPECT_EQ(std::string("[").size(), newString->value->spans[3].firstChar);
- EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(), newString->value->spans[3].lastChar);
+ EXPECT_EQ(std::string("[").size(), newString->value->spans[3].firstChar);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(),
+ newString->value->spans[3].lastChar);
}
TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .addString("android:string/one", "one")
- .addString("android:string/two", ResourceId{}, test::parseConfigOrDie("en"), "two")
- .addString("android:string/three", "three")
- .addString("android:string/three", ResourceId{}, test::parseConfigOrDie("en-rXA"),
- "three")
- .addString("android:string/four", "four")
- .build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .addString("android:string/one", "one")
+ .addString("android:string/two", ResourceId{},
+ test::parseConfigOrDie("en"), "two")
+ .addString("android:string/three", "three")
+ .addString("android:string/three", ResourceId{},
+ test::parseConfigOrDie("en-rXA"), "three")
+ .addString("android:string/four", "four")
+ .build();
- String* val = test::getValue<String>(table.get(), "android:string/four");
- ASSERT_NE(nullptr, val);
- val->setTranslateable(false);
+ String* val = test::getValue<String>(table.get(), "android:string/four");
+ ASSERT_NE(nullptr, val);
+ val->setTranslateable(false);
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- PseudolocaleGenerator generator;
- ASSERT_TRUE(generator.consume(context.get(), table.get()));
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ PseudolocaleGenerator generator;
+ ASSERT_TRUE(generator.consume(context.get(), table.get()));
- // Normal pseudolocalization should take place.
- ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/one",
- test::parseConfigOrDie("en-rXA")));
- ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/one",
- test::parseConfigOrDie("ar-rXB")));
+ // Normal pseudolocalization should take place.
+ ASSERT_NE(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/one",
+ test::parseConfigOrDie("en-rXA")));
+ ASSERT_NE(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/one",
+ test::parseConfigOrDie("ar-rXB")));
- // No default config for android:string/two, so no pseudlocales should exist.
- ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/two",
- test::parseConfigOrDie("en-rXA")));
- ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/two",
- test::parseConfigOrDie("ar-rXB")));
+ // No default config for android:string/two, so no pseudlocales should exist.
+ ASSERT_EQ(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/two",
+ test::parseConfigOrDie("en-rXA")));
+ ASSERT_EQ(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/two",
+ test::parseConfigOrDie("ar-rXB")));
+ // Check that we didn't override manual pseudolocalization.
+ val = test::getValueForConfig<String>(table.get(), "android:string/three",
+ test::parseConfigOrDie("en-rXA"));
+ ASSERT_NE(nullptr, val);
+ EXPECT_EQ(std::string("three"), *val->value);
- // Check that we didn't override manual pseudolocalization.
- val = test::getValueForConfig<String>(table.get(), "android:string/three",
- test::parseConfigOrDie("en-rXA"));
- ASSERT_NE(nullptr, val);
- EXPECT_EQ(std::string("three"), *val->value);
+ ASSERT_NE(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/three",
+ test::parseConfigOrDie("ar-rXB")));
- ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/three",
- test::parseConfigOrDie("ar-rXB")));
-
- // Check that four's translateable marker was honored.
- ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/four",
- test::parseConfigOrDie("en-rXA")));
- ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/four",
- test::parseConfigOrDie("ar-rXB")));
-
+ // Check that four's translateable marker was honored.
+ ASSERT_EQ(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/four",
+ test::parseConfigOrDie("en-rXA")));
+ ASSERT_EQ(nullptr,
+ test::getValueForConfig<String>(table.get(), "android:string/four",
+ test::parseConfigOrDie("ar-rXB")));
}
-} // namespace aapt
-
+} // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp
index 90d0d85..c3aec98 100644
--- a/tools/aapt2/compile/Pseudolocalizer.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer.cpp
@@ -20,9 +20,10 @@
namespace aapt {
// String basis to generate expansion
-static const std::string k_expansion_string = "one two three "
- "four five six seven eight nine ten eleven twelve thirteen "
- "fourteen fiveteen sixteen seventeen nineteen twenty";
+static const std::string k_expansion_string =
+ "one two three "
+ "four five six seven eight nine ten eleven twelve thirteen "
+ "fourteen fiveteen sixteen seventeen nineteen twenty";
// Special unicode characters to override directionality of the words
static const std::string k_rlm = "\u200f";
@@ -37,229 +38,310 @@
static const char k_arg_end = '}';
class PseudoMethodNone : public PseudoMethodImpl {
-public:
- std::string text(const StringPiece& text) override { return text.toString(); }
- std::string placeholder(const StringPiece& text) override { return text.toString(); }
+ public:
+ std::string text(const StringPiece& text) override { return text.toString(); }
+ std::string placeholder(const StringPiece& text) override {
+ return text.toString();
+ }
};
class PseudoMethodBidi : public PseudoMethodImpl {
-public:
- std::string text(const StringPiece& text) override;
- std::string placeholder(const StringPiece& text) override;
+ public:
+ std::string text(const StringPiece& text) override;
+ std::string placeholder(const StringPiece& text) override;
};
class PseudoMethodAccent : public PseudoMethodImpl {
-public:
- PseudoMethodAccent() : mDepth(0), mWordCount(0), mLength(0) {}
- std::string start() override;
- std::string end() override;
- std::string text(const StringPiece& text) override;
- std::string placeholder(const StringPiece& text) override;
-private:
- size_t mDepth;
- size_t mWordCount;
- size_t mLength;
+ public:
+ PseudoMethodAccent() : mDepth(0), mWordCount(0), mLength(0) {}
+ std::string start() override;
+ std::string end() override;
+ std::string text(const StringPiece& text) override;
+ std::string placeholder(const StringPiece& text) override;
+
+ private:
+ size_t mDepth;
+ size_t mWordCount;
+ size_t mLength;
};
Pseudolocalizer::Pseudolocalizer(Method method) : mLastDepth(0) {
- setMethod(method);
+ setMethod(method);
}
void Pseudolocalizer::setMethod(Method method) {
- switch (method) {
+ switch (method) {
case Method::kNone:
- mImpl = util::make_unique<PseudoMethodNone>();
- break;
+ mImpl = util::make_unique<PseudoMethodNone>();
+ break;
case Method::kAccent:
- mImpl = util::make_unique<PseudoMethodAccent>();
- break;
+ mImpl = util::make_unique<PseudoMethodAccent>();
+ break;
case Method::kBidi:
- mImpl = util::make_unique<PseudoMethodBidi>();
- break;
- }
+ mImpl = util::make_unique<PseudoMethodBidi>();
+ break;
+ }
}
std::string Pseudolocalizer::text(const StringPiece& text) {
- std::string out;
- size_t depth = mLastDepth;
- size_t lastpos, pos;
- const size_t length = text.size();
- const char* str = text.data();
- bool escaped = false;
- for (lastpos = pos = 0; pos < length; pos++) {
- char16_t c = str[pos];
- if (escaped) {
- escaped = false;
- continue;
- }
- if (c == '\'') {
- escaped = true;
- continue;
- }
-
- if (c == k_arg_start) {
- depth++;
- } else if (c == k_arg_end && depth) {
- depth--;
- }
-
- if (mLastDepth != depth || pos == length - 1) {
- bool pseudo = ((mLastDepth % 2) == 0);
- size_t nextpos = pos;
- if (!pseudo || depth == mLastDepth) {
- nextpos++;
- }
- size_t size = nextpos - lastpos;
- if (size) {
- std::string chunk = text.substr(lastpos, size).toString();
- if (pseudo) {
- chunk = mImpl->text(chunk);
- } else if (str[lastpos] == k_arg_start && str[nextpos - 1] == k_arg_end) {
- chunk = mImpl->placeholder(chunk);
- }
- out.append(chunk);
- }
- if (pseudo && depth < mLastDepth) { // End of message
- out.append(mImpl->end());
- } else if (!pseudo && depth > mLastDepth) { // Start of message
- out.append(mImpl->start());
- }
- lastpos = nextpos;
- mLastDepth = depth;
- }
+ std::string out;
+ size_t depth = mLastDepth;
+ size_t lastpos, pos;
+ const size_t length = text.size();
+ const char* str = text.data();
+ bool escaped = false;
+ for (lastpos = pos = 0; pos < length; pos++) {
+ char16_t c = str[pos];
+ if (escaped) {
+ escaped = false;
+ continue;
}
- return out;
+ if (c == '\'') {
+ escaped = true;
+ continue;
+ }
+
+ if (c == k_arg_start) {
+ depth++;
+ } else if (c == k_arg_end && depth) {
+ depth--;
+ }
+
+ if (mLastDepth != depth || pos == length - 1) {
+ bool pseudo = ((mLastDepth % 2) == 0);
+ size_t nextpos = pos;
+ if (!pseudo || depth == mLastDepth) {
+ nextpos++;
+ }
+ size_t size = nextpos - lastpos;
+ if (size) {
+ std::string chunk = text.substr(lastpos, size).toString();
+ if (pseudo) {
+ chunk = mImpl->text(chunk);
+ } else if (str[lastpos] == k_arg_start &&
+ str[nextpos - 1] == k_arg_end) {
+ chunk = mImpl->placeholder(chunk);
+ }
+ out.append(chunk);
+ }
+ if (pseudo && depth < mLastDepth) { // End of message
+ out.append(mImpl->end());
+ } else if (!pseudo && depth > mLastDepth) { // Start of message
+ out.append(mImpl->start());
+ }
+ lastpos = nextpos;
+ mLastDepth = depth;
+ }
+ }
+ return out;
}
static const char* pseudolocalizeChar(const char c) {
- switch (c) {
- case 'a': return "\u00e5";
- case 'b': return "\u0253";
- case 'c': return "\u00e7";
- case 'd': return "\u00f0";
- case 'e': return "\u00e9";
- case 'f': return "\u0192";
- case 'g': return "\u011d";
- case 'h': return "\u0125";
- case 'i': return "\u00ee";
- case 'j': return "\u0135";
- case 'k': return "\u0137";
- case 'l': return "\u013c";
- case 'm': return "\u1e3f";
- case 'n': return "\u00f1";
- case 'o': return "\u00f6";
- case 'p': return "\u00fe";
- case 'q': return "\u0051";
- case 'r': return "\u0155";
- case 's': return "\u0161";
- case 't': return "\u0163";
- case 'u': return "\u00fb";
- case 'v': return "\u0056";
- case 'w': return "\u0175";
- case 'x': return "\u0445";
- case 'y': return "\u00fd";
- case 'z': return "\u017e";
- case 'A': return "\u00c5";
- case 'B': return "\u03b2";
- case 'C': return "\u00c7";
- case 'D': return "\u00d0";
- case 'E': return "\u00c9";
- case 'G': return "\u011c";
- case 'H': return "\u0124";
- case 'I': return "\u00ce";
- case 'J': return "\u0134";
- case 'K': return "\u0136";
- case 'L': return "\u013b";
- case 'M': return "\u1e3e";
- case 'N': return "\u00d1";
- case 'O': return "\u00d6";
- case 'P': return "\u00de";
- case 'Q': return "\u0071";
- case 'R': return "\u0154";
- case 'S': return "\u0160";
- case 'T': return "\u0162";
- case 'U': return "\u00db";
- case 'V': return "\u03bd";
- case 'W': return "\u0174";
- case 'X': return "\u00d7";
- case 'Y': return "\u00dd";
- case 'Z': return "\u017d";
- case '!': return "\u00a1";
- case '?': return "\u00bf";
- case '$': return "\u20ac";
- default: return nullptr;
- }
+ switch (c) {
+ case 'a':
+ return "\u00e5";
+ case 'b':
+ return "\u0253";
+ case 'c':
+ return "\u00e7";
+ case 'd':
+ return "\u00f0";
+ case 'e':
+ return "\u00e9";
+ case 'f':
+ return "\u0192";
+ case 'g':
+ return "\u011d";
+ case 'h':
+ return "\u0125";
+ case 'i':
+ return "\u00ee";
+ case 'j':
+ return "\u0135";
+ case 'k':
+ return "\u0137";
+ case 'l':
+ return "\u013c";
+ case 'm':
+ return "\u1e3f";
+ case 'n':
+ return "\u00f1";
+ case 'o':
+ return "\u00f6";
+ case 'p':
+ return "\u00fe";
+ case 'q':
+ return "\u0051";
+ case 'r':
+ return "\u0155";
+ case 's':
+ return "\u0161";
+ case 't':
+ return "\u0163";
+ case 'u':
+ return "\u00fb";
+ case 'v':
+ return "\u0056";
+ case 'w':
+ return "\u0175";
+ case 'x':
+ return "\u0445";
+ case 'y':
+ return "\u00fd";
+ case 'z':
+ return "\u017e";
+ case 'A':
+ return "\u00c5";
+ case 'B':
+ return "\u03b2";
+ case 'C':
+ return "\u00c7";
+ case 'D':
+ return "\u00d0";
+ case 'E':
+ return "\u00c9";
+ case 'G':
+ return "\u011c";
+ case 'H':
+ return "\u0124";
+ case 'I':
+ return "\u00ce";
+ case 'J':
+ return "\u0134";
+ case 'K':
+ return "\u0136";
+ case 'L':
+ return "\u013b";
+ case 'M':
+ return "\u1e3e";
+ case 'N':
+ return "\u00d1";
+ case 'O':
+ return "\u00d6";
+ case 'P':
+ return "\u00de";
+ case 'Q':
+ return "\u0071";
+ case 'R':
+ return "\u0154";
+ case 'S':
+ return "\u0160";
+ case 'T':
+ return "\u0162";
+ case 'U':
+ return "\u00db";
+ case 'V':
+ return "\u03bd";
+ case 'W':
+ return "\u0174";
+ case 'X':
+ return "\u00d7";
+ case 'Y':
+ return "\u00dd";
+ case 'Z':
+ return "\u017d";
+ case '!':
+ return "\u00a1";
+ case '?':
+ return "\u00bf";
+ case '$':
+ return "\u20ac";
+ default:
+ return nullptr;
+ }
}
static bool isPossibleNormalPlaceholderEnd(const char c) {
- switch (c) {
- case 's': return true;
- case 'S': return true;
- case 'c': return true;
- case 'C': return true;
- case 'd': return true;
- case 'o': return true;
- case 'x': return true;
- case 'X': return true;
- case 'f': return true;
- case 'e': return true;
- case 'E': return true;
- case 'g': return true;
- case 'G': return true;
- case 'a': return true;
- case 'A': return true;
- case 'b': return true;
- case 'B': return true;
- case 'h': return true;
- case 'H': return true;
- case '%': return true;
- case 'n': return true;
- default: return false;
- }
+ switch (c) {
+ case 's':
+ return true;
+ case 'S':
+ return true;
+ case 'c':
+ return true;
+ case 'C':
+ return true;
+ case 'd':
+ return true;
+ case 'o':
+ return true;
+ case 'x':
+ return true;
+ case 'X':
+ return true;
+ case 'f':
+ return true;
+ case 'e':
+ return true;
+ case 'E':
+ return true;
+ case 'g':
+ return true;
+ case 'G':
+ return true;
+ case 'a':
+ return true;
+ case 'A':
+ return true;
+ case 'b':
+ return true;
+ case 'B':
+ return true;
+ case 'h':
+ return true;
+ case 'H':
+ return true;
+ case '%':
+ return true;
+ case 'n':
+ return true;
+ default:
+ return false;
+ }
}
static std::string pseudoGenerateExpansion(const unsigned int length) {
- std::string result = k_expansion_string;
- const char* s = result.data();
- if (result.size() < length) {
- result += " ";
- result += pseudoGenerateExpansion(length - result.size());
- } else {
- int ext = 0;
- // Should contain only whole words, so looking for a space
- for (unsigned int i = length + 1; i < result.size(); ++i) {
- ++ext;
- if (s[i] == ' ') {
- break;
- }
- }
- result = result.substr(0, length + ext);
+ std::string result = k_expansion_string;
+ const char* s = result.data();
+ if (result.size() < length) {
+ result += " ";
+ result += pseudoGenerateExpansion(length - result.size());
+ } else {
+ int ext = 0;
+ // Should contain only whole words, so looking for a space
+ for (unsigned int i = length + 1; i < result.size(); ++i) {
+ ++ext;
+ if (s[i] == ' ') {
+ break;
+ }
}
- return result;
+ result = result.substr(0, length + ext);
+ }
+ return result;
}
std::string PseudoMethodAccent::start() {
- std::string result;
- if (mDepth == 0) {
- result = "[";
- }
- mWordCount = mLength = 0;
- mDepth++;
- return result;
+ std::string result;
+ if (mDepth == 0) {
+ result = "[";
+ }
+ mWordCount = mLength = 0;
+ mDepth++;
+ return result;
}
std::string PseudoMethodAccent::end() {
- std::string result;
- if (mLength) {
- result += " ";
- result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2);
- }
- mWordCount = mLength = 0;
- mDepth--;
- if (mDepth == 0) {
- result += "]";
- }
- return result;
+ std::string result;
+ if (mLength) {
+ result += " ";
+ result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2);
+ }
+ mWordCount = mLength = 0;
+ mDepth--;
+ if (mDepth == 0) {
+ result += "]";
+ }
+ return result;
}
/**
@@ -267,128 +349,125 @@
*
* Note: This leaves placeholder syntax untouched.
*/
-std::string PseudoMethodAccent::text(const StringPiece& source)
-{
- const char* s = source.data();
- std::string result;
- const size_t I = source.size();
- bool lastspace = true;
- for (size_t i = 0; i < I; i++) {
- char c = s[i];
- if (c == '%') {
- // Placeholder syntax, no need to pseudolocalize
- std::string chunk;
- bool end = false;
- chunk.append(&c, 1);
- while (!end && i + 1 < I) {
- ++i;
- c = s[i];
- chunk.append(&c, 1);
- if (isPossibleNormalPlaceholderEnd(c)) {
- end = true;
- } else if (i + 1 < I && c == 't') {
- ++i;
- c = s[i];
- chunk.append(&c, 1);
- end = true;
- }
- }
- // Treat chunk as a placeholder unless it ends with %.
- result += ((c == '%') ? chunk : placeholder(chunk));
- } else if (c == '<' || c == '&') {
- // html syntax, no need to pseudolocalize
- bool tag_closed = false;
- while (!tag_closed && i < I) {
- if (c == '&') {
- std::string escapeText;
- escapeText.append(&c, 1);
- bool end = false;
- size_t htmlCodePos = i;
- while (!end && htmlCodePos < I) {
- ++htmlCodePos;
- c = s[htmlCodePos];
- escapeText.append(&c, 1);
- // Valid html code
- if (c == ';') {
- end = true;
- i = htmlCodePos;
- }
- // Wrong html code
- else if (!((c == '#' ||
- (c >= 'a' && c <= 'z') ||
- (c >= 'A' && c <= 'Z') ||
- (c >= '0' && c <= '9')))) {
- end = true;
- }
- }
- result += escapeText;
- if (escapeText != "<") {
- tag_closed = true;
- }
- continue;
- }
- if (c == '>') {
- tag_closed = true;
- result.append(&c, 1);
- continue;
- }
- result.append(&c, 1);
- i++;
- c = s[i];
- }
- } else {
- // This is a pure text that should be pseudolocalized
- const char* p = pseudolocalizeChar(c);
- if (p != nullptr) {
- result += p;
- } else {
- bool space = isspace(c);
- if (lastspace && !space) {
- mWordCount++;
- }
- lastspace = space;
- result.append(&c, 1);
- }
- // Count only pseudolocalizable chars and delimiters
- mLength++;
+std::string PseudoMethodAccent::text(const StringPiece& source) {
+ const char* s = source.data();
+ std::string result;
+ const size_t I = source.size();
+ bool lastspace = true;
+ for (size_t i = 0; i < I; i++) {
+ char c = s[i];
+ if (c == '%') {
+ // Placeholder syntax, no need to pseudolocalize
+ std::string chunk;
+ bool end = false;
+ chunk.append(&c, 1);
+ while (!end && i + 1 < I) {
+ ++i;
+ c = s[i];
+ chunk.append(&c, 1);
+ if (isPossibleNormalPlaceholderEnd(c)) {
+ end = true;
+ } else if (i + 1 < I && c == 't') {
+ ++i;
+ c = s[i];
+ chunk.append(&c, 1);
+ end = true;
}
- }
- return result;
-}
-
-std::string PseudoMethodAccent::placeholder(const StringPiece& source) {
- // Surround a placeholder with brackets
- return k_placeholder_open + source.toString() + k_placeholder_close;
-}
-
-std::string PseudoMethodBidi::text(const StringPiece& source) {
- const char* s = source.data();
- std::string result;
- bool lastspace = true;
- bool space = true;
- for (size_t i = 0; i < source.size(); i++) {
- char c = s[i];
- space = isspace(c);
+ }
+ // Treat chunk as a placeholder unless it ends with %.
+ result += ((c == '%') ? chunk : placeholder(chunk));
+ } else if (c == '<' || c == '&') {
+ // html syntax, no need to pseudolocalize
+ bool tag_closed = false;
+ while (!tag_closed && i < I) {
+ if (c == '&') {
+ std::string escapeText;
+ escapeText.append(&c, 1);
+ bool end = false;
+ size_t htmlCodePos = i;
+ while (!end && htmlCodePos < I) {
+ ++htmlCodePos;
+ c = s[htmlCodePos];
+ escapeText.append(&c, 1);
+ // Valid html code
+ if (c == ';') {
+ end = true;
+ i = htmlCodePos;
+ }
+ // Wrong html code
+ else if (!((c == '#' || (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')))) {
+ end = true;
+ }
+ }
+ result += escapeText;
+ if (escapeText != "<") {
+ tag_closed = true;
+ }
+ continue;
+ }
+ if (c == '>') {
+ tag_closed = true;
+ result.append(&c, 1);
+ continue;
+ }
+ result.append(&c, 1);
+ i++;
+ c = s[i];
+ }
+ } else {
+ // This is a pure text that should be pseudolocalized
+ const char* p = pseudolocalizeChar(c);
+ if (p != nullptr) {
+ result += p;
+ } else {
+ bool space = isspace(c);
if (lastspace && !space) {
- // Word start
- result += k_rlm + k_rlo;
- } else if (!lastspace && space) {
- // Word end
- result += k_pdf + k_rlm;
+ mWordCount++;
}
lastspace = space;
result.append(&c, 1);
+ }
+ // Count only pseudolocalizable chars and delimiters
+ mLength++;
}
- if (!lastspace) {
- // End of last word
- result += k_pdf + k_rlm;
+ }
+ return result;
+}
+
+std::string PseudoMethodAccent::placeholder(const StringPiece& source) {
+ // Surround a placeholder with brackets
+ return k_placeholder_open + source.toString() + k_placeholder_close;
+}
+
+std::string PseudoMethodBidi::text(const StringPiece& source) {
+ const char* s = source.data();
+ std::string result;
+ bool lastspace = true;
+ bool space = true;
+ for (size_t i = 0; i < source.size(); i++) {
+ char c = s[i];
+ space = isspace(c);
+ if (lastspace && !space) {
+ // Word start
+ result += k_rlm + k_rlo;
+ } else if (!lastspace && space) {
+ // Word end
+ result += k_pdf + k_rlm;
}
- return result;
+ lastspace = space;
+ result.append(&c, 1);
+ }
+ if (!lastspace) {
+ // End of last word
+ result += k_pdf + k_rlm;
+ }
+ return result;
}
std::string PseudoMethodBidi::placeholder(const StringPiece& source) {
- // Surround a placeholder with directionality change sequence
- return k_rlm + k_rlo + source.toString() + k_pdf + k_rlm;
+ // Surround a placeholder with directionality change sequence
+ return k_rlm + k_rlo + source.toString() + k_pdf + k_rlm;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h
index 91d17d174..a526877 100644
--- a/tools/aapt2/compile/Pseudolocalizer.h
+++ b/tools/aapt2/compile/Pseudolocalizer.h
@@ -27,32 +27,33 @@
namespace aapt {
class PseudoMethodImpl {
-public:
- virtual ~PseudoMethodImpl() {}
- virtual std::string start() { return {}; }
- virtual std::string end() { return {}; }
- virtual std::string text(const StringPiece& text) = 0;
- virtual std::string placeholder(const StringPiece& text) = 0;
+ public:
+ virtual ~PseudoMethodImpl() {}
+ virtual std::string start() { return {}; }
+ virtual std::string end() { return {}; }
+ virtual std::string text(const StringPiece& text) = 0;
+ virtual std::string placeholder(const StringPiece& text) = 0;
};
class Pseudolocalizer {
-public:
- enum class Method {
- kNone,
- kAccent,
- kBidi,
- };
+ public:
+ enum class Method {
+ kNone,
+ kAccent,
+ kBidi,
+ };
- explicit Pseudolocalizer(Method method);
- void setMethod(Method method);
- std::string start() { return mImpl->start(); }
- std::string end() { return mImpl->end(); }
- std::string text(const StringPiece& text);
-private:
- std::unique_ptr<PseudoMethodImpl> mImpl;
- size_t mLastDepth;
+ explicit Pseudolocalizer(Method method);
+ void setMethod(Method method);
+ std::string start() { return mImpl->start(); }
+ std::string end() { return mImpl->end(); }
+ std::string text(const StringPiece& text);
+
+ private:
+ std::unique_ptr<PseudoMethodImpl> mImpl;
+ size_t mLastDepth;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_COMPILE_PSEUDOLOCALIZE_H */
diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp
index c33e152..a152ed6 100644
--- a/tools/aapt2/compile/Pseudolocalizer_test.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp
@@ -25,199 +25,207 @@
// In this context, 'Axis' represents a particular field in the configuration,
// such as language or density.
-static ::testing::AssertionResult simpleHelper(const char* input, const char* expected,
+static ::testing::AssertionResult simpleHelper(const char* input,
+ const char* expected,
Pseudolocalizer::Method method) {
- Pseudolocalizer pseudo(method);
- std::string result = pseudo.start() + pseudo.text(input) + pseudo.end();
- if (result != expected) {
- return ::testing::AssertionFailure() << expected << " != " << result;
- }
- return ::testing::AssertionSuccess();
+ Pseudolocalizer pseudo(method);
+ std::string result = pseudo.start() + pseudo.text(input) + pseudo.end();
+ if (result != expected) {
+ return ::testing::AssertionFailure() << expected << " != " << result;
+ }
+ return ::testing::AssertionSuccess();
}
-static ::testing::AssertionResult compoundHelper(const char* in1, const char* in2, const char *in3,
- const char* expected,
- Pseudolocalizer::Method method) {
- Pseudolocalizer pseudo(method);
- std::string result = pseudo.start() + pseudo.text(in1) + pseudo.text(in2) + pseudo.text(in3) +
- pseudo.end();
- if (result != expected) {
- return ::testing::AssertionFailure() << expected << " != " << result;
- }
- return ::testing::AssertionSuccess();
+static ::testing::AssertionResult compoundHelper(
+ const char* in1, const char* in2, const char* in3, const char* expected,
+ Pseudolocalizer::Method method) {
+ Pseudolocalizer pseudo(method);
+ std::string result = pseudo.start() + pseudo.text(in1) + pseudo.text(in2) +
+ pseudo.text(in3) + pseudo.end();
+ if (result != expected) {
+ return ::testing::AssertionFailure() << expected << " != " << result;
+ }
+ return ::testing::AssertionSuccess();
}
TEST(PseudolocalizerTest, NoPseudolocalization) {
- EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone));
- EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world", Pseudolocalizer::Method::kNone));
+ EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone));
+ EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world",
+ Pseudolocalizer::Method::kNone));
- EXPECT_TRUE(compoundHelper("Hello,", " world", "",
- "Hello, world", Pseudolocalizer::Method::kNone));
+ EXPECT_TRUE(compoundHelper("Hello,", " world", "", "Hello, world",
+ Pseudolocalizer::Method::kNone));
}
TEST(PseudolocalizerTest, PlaintextAccent) {
- EXPECT_TRUE(simpleHelper("", "[]", Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("Hello, world",
- "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("", "[]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Hello, world", "[Ĥéļļö, ŵöŕļð one two]",
+ Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("Hello, %1d",
- "[Ĥéļļö, »%1d« one two]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Hello, %1d", "[Ĥéļļö, »%1d« one two]",
+ Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("Battery %1d%%",
- "[βåţţéŕý »%1d«%% one two]", Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(compoundHelper("Hello,", " world", "",
- "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Battery %1d%%", "[βåţţéŕý »%1d«%% one two]",
+ Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(
+ simpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(
+ compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(compoundHelper("Hello,", " world", "", "[Ĥéļļö, ŵöŕļð one two]",
+ Pseudolocalizer::Method::kAccent));
}
TEST(PseudolocalizerTest, PlaintextBidi) {
- EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(simpleHelper("word",
- "\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f",
- Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(simpleHelper(" word ",
- " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
- Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(simpleHelper(" word ",
- " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
- Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(simpleHelper("hello\n world\n",
- "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \
- " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
- Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(compoundHelper("hello", "\n ", " world\n",
- "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \
- " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
- Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper(
+ "word", "\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper(
+ " word ", " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper(
+ " word ", " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(
+ simpleHelper("hello\n world\n",
+ "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n"
+ " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(compoundHelper(
+ "hello", "\n ", " world\n",
+ "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n"
+ " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n",
+ Pseudolocalizer::Method::kBidi));
}
TEST(PseudolocalizerTest, SimpleICU) {
- // Single-fragment messages
- EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]",
+ // Single-fragment messages
+ EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]",
+ Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("{USER} is offline", "[»{USER}« îš öƒƒļîñé one two]",
+ Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}",
+ "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
+ Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(simpleHelper("Today is {1,date} {1,time}",
+ "[Ţöðåý îš »{1,date}« »{1,time}« one two]",
+ Pseudolocalizer::Method::kAccent));
+
+ // Multi-fragment messages
+ EXPECT_TRUE(compoundHelper("{USER}", " ", "is offline",
+ "[»{USER}« îš öƒƒļîñé one two]",
Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("{USER} is offline",
- "[»{USER}« îš öƒƒļîñé one two]", Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}",
+ EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}",
"[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(simpleHelper("Today is {1,date} {1,time}",
- "[Ţöðåý îš »{1,date}« »{1,time}« one two]",
- Pseudolocalizer::Method::kAccent));
-
- // Multi-fragment messages
- EXPECT_TRUE(compoundHelper("{USER}", " ", "is offline",
- "[»{USER}« îš öƒƒļîñé one two]",
- Pseudolocalizer::Method::kAccent));
- EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}",
- "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
- Pseudolocalizer::Method::kAccent));
}
TEST(PseudolocalizerTest, ICUBidi) {
- // Single-fragment messages
- EXPECT_TRUE(simpleHelper("{placeholder}",
- "\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f",
- Pseudolocalizer::Method::kBidi));
- EXPECT_TRUE(simpleHelper(
- "{COUNT, plural, one {one} other {other}}",
- "{COUNT, plural, " \
- "one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} " \
- "other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}",
- Pseudolocalizer::Method::kBidi));
+ // Single-fragment messages
+ EXPECT_TRUE(simpleHelper(
+ "{placeholder}",
+ "\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f",
+ Pseudolocalizer::Method::kBidi));
+ EXPECT_TRUE(simpleHelper(
+ "{COUNT, plural, one {one} other {other}}",
+ "{COUNT, plural, "
+ "one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} "
+ "other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}",
+ Pseudolocalizer::Method::kBidi));
}
TEST(PseudolocalizerTest, Escaping) {
- // Single-fragment messages
- EXPECT_TRUE(simpleHelper("'{USER'} is offline",
- "['{ÛŠÉŔ'} îš öƒƒļîñé one two three]",
- Pseudolocalizer::Method::kAccent));
+ // Single-fragment messages
+ EXPECT_TRUE(simpleHelper("'{USER'} is offline",
+ "['{ÛŠÉŔ'} îš öƒƒļîñé one two three]",
+ Pseudolocalizer::Method::kAccent));
- // Multi-fragment messages
- EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline",
- "['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]",
- Pseudolocalizer::Method::kAccent));
+ // Multi-fragment messages
+ EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline",
+ "['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]",
+ Pseudolocalizer::Method::kAccent));
}
TEST(PseudolocalizerTest, PluralsAndSelects) {
- EXPECT_TRUE(simpleHelper(
- "{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}",
- "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \
+ EXPECT_TRUE(simpleHelper(
+ "{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}",
+ "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} "
+ "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
+ Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(
+ simpleHelper("Distance is {COUNT, plural, one {# mile} other {# miles}}",
+ "[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} "
+ "other {# ḿîļéš one two}}]",
+ Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(simpleHelper(
+ "{1, select, female {{1} added you} "
+ "male {{1} added you} other {{1} added you}}",
+ "[{1, select, female {»{1}« åððéð ýöû one two} "
+ "male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]",
+ Pseudolocalizer::Method::kAccent));
+
+ EXPECT_TRUE(
+ compoundHelper("{COUNT, plural, one {Delete a file} "
+ "other {Delete ",
+ "{COUNT}", " files}}",
+ "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} "
"other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
- Pseudolocalizer::Method::kAccent));
-
- EXPECT_TRUE(simpleHelper(
- "Distance is {COUNT, plural, one {# mile} other {# miles}}",
- "[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} " \
- "other {# ḿîļéš one two}}]",
- Pseudolocalizer::Method::kAccent));
-
- EXPECT_TRUE(simpleHelper(
- "{1, select, female {{1} added you} " \
- "male {{1} added you} other {{1} added you}}",
- "[{1, select, female {»{1}« åððéð ýöû one two} " \
- "male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]",
- Pseudolocalizer::Method::kAccent));
-
- EXPECT_TRUE(compoundHelper(
- "{COUNT, plural, one {Delete a file} " \
- "other {Delete ", "{COUNT}", " files}}",
- "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \
- "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
- Pseudolocalizer::Method::kAccent));
+ Pseudolocalizer::Method::kAccent));
}
TEST(PseudolocalizerTest, NestedICU) {
- EXPECT_TRUE(simpleHelper(
- "{person, select, " \
- "female {" \
- "{num_circles, plural," \
- "=0{{person} didn't add you to any of her circles.}" \
- "=1{{person} added you to one of her circles.}" \
- "other{{person} added you to her # circles.}}}" \
- "male {" \
- "{num_circles, plural," \
- "=0{{person} didn't add you to any of his circles.}" \
- "=1{{person} added you to one of his circles.}" \
- "other{{person} added you to his # circles.}}}" \
- "other {" \
- "{num_circles, plural," \
- "=0{{person} didn't add you to any of their circles.}" \
- "=1{{person} added you to one of their circles.}" \
- "other{{person} added you to their # circles.}}}}",
- "[{person, select, " \
- "female {" \
- "{num_circles, plural," \
- "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš." \
- " one two three four five}" \
- "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš." \
- " one two three four}" \
- "other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš." \
- " one two three four}}}" \
- "male {" \
- "{num_circles, plural," \
- "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš." \
- " one two three four five}" \
- "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš." \
- " one two three four}" \
- "other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš." \
- " one two three four}}}" \
- "other {{num_circles, plural," \
- "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš." \
- " one two three four five}" \
- "=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš." \
- " one two three four}" \
- "other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš." \
- " one two three four}}}}]",
- Pseudolocalizer::Method::kAccent));
+ EXPECT_TRUE(
+ simpleHelper("{person, select, "
+ "female {"
+ "{num_circles, plural,"
+ "=0{{person} didn't add you to any of her circles.}"
+ "=1{{person} added you to one of her circles.}"
+ "other{{person} added you to her # circles.}}}"
+ "male {"
+ "{num_circles, plural,"
+ "=0{{person} didn't add you to any of his circles.}"
+ "=1{{person} added you to one of his circles.}"
+ "other{{person} added you to his # circles.}}}"
+ "other {"
+ "{num_circles, plural,"
+ "=0{{person} didn't add you to any of their circles.}"
+ "=1{{person} added you to one of their circles.}"
+ "other{{person} added you to their # circles.}}}}",
+ "[{person, select, "
+ "female {"
+ "{num_circles, plural,"
+ "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš."
+ " one two three four five}"
+ "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš."
+ " one two three four}"
+ "other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš."
+ " one two three four}}}"
+ "male {"
+ "{num_circles, plural,"
+ "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš."
+ " one two three four five}"
+ "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš."
+ " one two three four}"
+ "other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš."
+ " one two three four}}}"
+ "other {{num_circles, plural,"
+ "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš."
+ " one two three four five}"
+ "=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš."
+ " one two three four}"
+ "other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš."
+ " one two three four}}}}]",
+ Pseudolocalizer::Method::kAccent));
}
TEST(PseudolocalizerTest, RedefineMethod) {
- Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent);
- std::string result = pseudo.text("Hello, ");
- pseudo.setMethod(Pseudolocalizer::Method::kNone);
- result += pseudo.text("world!");
- ASSERT_EQ(StringPiece("Ĥéļļö, world!"), result);
+ Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent);
+ std::string result = pseudo.text("Hello, ");
+ pseudo.setMethod(Pseudolocalizer::Method::kNone);
+ result += pseudo.text("world!");
+ ASSERT_EQ(StringPiece("Ĥéļļö, world!"), result);
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp
index 3901419..aa8b1df 100644
--- a/tools/aapt2/compile/XmlIdCollector.cpp
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
+#include "compile/XmlIdCollector.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "compile/XmlIdCollector.h"
#include "xml/XmlDom.h"
#include <algorithm>
@@ -27,44 +27,44 @@
namespace {
static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) {
- return a.name < b;
+ return a.name < b;
}
struct IdCollector : public xml::Visitor {
- using xml::Visitor::visit;
+ using xml::Visitor::visit;
- std::vector<SourcedResourceName>* mOutSymbols;
+ std::vector<SourcedResourceName>* mOutSymbols;
- explicit IdCollector(std::vector<SourcedResourceName>* outSymbols) : mOutSymbols(outSymbols) {
- }
+ explicit IdCollector(std::vector<SourcedResourceName>* outSymbols)
+ : mOutSymbols(outSymbols) {}
- void visit(xml::Element* element) override {
- for (xml::Attribute& attr : element->attributes) {
- ResourceNameRef name;
- bool create = false;
- if (ResourceUtils::parseReference(attr.value, &name, &create, nullptr)) {
- if (create && name.type == ResourceType::kId) {
- auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(),
- name, cmpName);
- if (iter == mOutSymbols->end() || iter->name != name) {
- mOutSymbols->insert(iter, SourcedResourceName{ name.toResourceName(),
- element->lineNumber });
- }
- }
- }
+ void visit(xml::Element* element) override {
+ for (xml::Attribute& attr : element->attributes) {
+ ResourceNameRef name;
+ bool create = false;
+ if (ResourceUtils::parseReference(attr.value, &name, &create, nullptr)) {
+ if (create && name.type == ResourceType::kId) {
+ auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(),
+ name, cmpName);
+ if (iter == mOutSymbols->end() || iter->name != name) {
+ mOutSymbols->insert(iter, SourcedResourceName{name.toResourceName(),
+ element->lineNumber});
+ }
}
-
- xml::Visitor::visit(element);
+ }
}
+
+ xml::Visitor::visit(element);
+ }
};
-} // namespace
+} // namespace
bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) {
- xmlRes->file.exportedSymbols.clear();
- IdCollector collector(&xmlRes->file.exportedSymbols);
- xmlRes->root->accept(&collector);
- return true;
+ xmlRes->file.exportedSymbols.clear();
+ IdCollector collector(&xmlRes->file.exportedSymbols);
+ xmlRes->root->accept(&collector);
+ return true;
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h
index 1b14944..8423f48 100644
--- a/tools/aapt2/compile/XmlIdCollector.h
+++ b/tools/aapt2/compile/XmlIdCollector.h
@@ -23,9 +23,9 @@
namespace aapt {
struct XmlIdCollector : public IXmlResourceConsumer {
- bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
+ bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
};
-} // namespace aapt
+} // namespace aapt
#endif /* AAPT_XMLIDCOLLECTOR_H */
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
index 2c9eab8..08ca7b1 100644
--- a/tools/aapt2/compile/XmlIdCollector_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -18,15 +18,15 @@
#include "test/Builders.h"
#include "test/Context.h"
-#include <algorithm>
#include <gtest/gtest.h>
+#include <algorithm>
namespace aapt {
TEST(XmlIdCollectorTest, CollectsIds) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/foo"
text="@+id/bar">
@@ -34,28 +34,35 @@
class="@+id/bar"/>
</View>)EOF");
- XmlIdCollector collector;
- ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+ XmlIdCollector collector;
+ ASSERT_TRUE(collector.consume(context.get(), doc.get()));
- EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
- SourcedResourceName{ test::parseNameOrDie("id/foo"), 3u }));
+ EXPECT_EQ(
+ 1, std::count(doc->file.exportedSymbols.begin(),
+ doc->file.exportedSymbols.end(),
+ SourcedResourceName{test::parseNameOrDie("id/foo"), 3u}));
- EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
- SourcedResourceName{ test::parseNameOrDie("id/bar"), 3u }));
+ EXPECT_EQ(
+ 1, std::count(doc->file.exportedSymbols.begin(),
+ doc->file.exportedSymbols.end(),
+ SourcedResourceName{test::parseNameOrDie("id/bar"), 3u}));
- EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
- SourcedResourceName{ test::parseNameOrDie("id/car"), 6u }));
+ EXPECT_EQ(
+ 1, std::count(doc->file.exportedSymbols.begin(),
+ doc->file.exportedSymbols.end(),
+ SourcedResourceName{test::parseNameOrDie("id/car"), 6u}));
}
TEST(XmlIdCollectorTest, DontCollectNonIds) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
+ std::unique_ptr<xml::XmlResource> doc =
+ test::buildXmlDom("<View foo=\"@+string/foo\"/>");
- XmlIdCollector collector;
- ASSERT_TRUE(collector.consume(context.get(), doc.get()));
+ XmlIdCollector collector;
+ ASSERT_TRUE(collector.consume(context.get(), doc.get()));
- EXPECT_TRUE(doc->file.exportedSymbols.empty());
+ EXPECT_TRUE(doc->file.exportedSymbols.empty());
}
-} // namespace aapt
+} // namespace aapt