| /* | |
| * Copyright 2019 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "src/utils/SkShaperJSONWriter.h" | |
| #include <algorithm> | |
| #include <limits> | |
| #include <string> | |
| #include "src/utils/SkJSONWriter.h" | |
| #include "src/utils/SkUTF.h" | |
| SkShaperJSONWriter::SkShaperJSONWriter(SkJSONWriter* JSONWriter, const char* utf8, size_t size) | |
| : fJSONWriter{JSONWriter} | |
| , fUTF8{utf8, size} {} | |
| void SkShaperJSONWriter::beginLine() { } | |
| void SkShaperJSONWriter::runInfo(const SkShaper::RunHandler::RunInfo& info) { } | |
| void SkShaperJSONWriter::commitRunInfo() { } | |
| SkShaper::RunHandler::Buffer | |
| SkShaperJSONWriter::runBuffer(const SkShaper::RunHandler::RunInfo& info) { | |
| fGlyphs.resize(info.glyphCount); | |
| fPositions.resize(info.glyphCount); | |
| fClusters.resize(info.glyphCount); | |
| return {fGlyphs.data(), fPositions.data(), nullptr, fClusters.data(), {0, 0}}; | |
| } | |
| static bool is_one_to_one(const char utf8[], size_t utf8Begin, size_t utf8End, | |
| std::vector<uint32_t>& clusters) { | |
| size_t lastUtf8Index = utf8End; | |
| auto checkCluster = [&](size_t clusterIndex) { | |
| if (clusters[clusterIndex] >= lastUtf8Index) { | |
| return false; | |
| } | |
| size_t utf8ClusterSize = lastUtf8Index - clusters[clusterIndex]; | |
| if (SkUTF::CountUTF8(&utf8[clusters[clusterIndex]], utf8ClusterSize) != 1) { | |
| return false; | |
| } | |
| lastUtf8Index = clusters[clusterIndex]; | |
| return true; | |
| }; | |
| if (clusters.front() <= clusters.back()) { | |
| // left-to-right clusters | |
| size_t clusterCursor = clusters.size(); | |
| while (clusterCursor > 0) { | |
| if (!checkCluster(--clusterCursor)) { return false; } | |
| } | |
| } else { | |
| // right-to-left clusters | |
| size_t clusterCursor = 0; | |
| while (clusterCursor < clusters.size()) { | |
| if (!checkCluster(clusterCursor++)) { return false; } | |
| } | |
| } | |
| return true; | |
| } | |
| void SkShaperJSONWriter::commitRunBuffer(const SkShaper::RunHandler::RunInfo& info) { | |
| fJSONWriter->beginObject("run", true); | |
| // Font name | |
| SkString fontName; | |
| info.fFont.getTypeface()->getFamilyName(&fontName); | |
| fJSONWriter->appendString("font name", fontName.c_str()); | |
| // Font size | |
| fJSONWriter->appendFloat("font size", info.fFont.getSize()); | |
| if (info.fBidiLevel > 0) { | |
| std::string bidiType = info.fBidiLevel % 2 == 0 ? "left-to-right" : "right-to-left"; | |
| std::string bidiOutput = bidiType + " lvl " + std::to_string(info.fBidiLevel); | |
| fJSONWriter->appendString("BiDi", bidiOutput.c_str()); | |
| } | |
| if (is_one_to_one(fUTF8.c_str(), info.utf8Range.begin(), info.utf8Range.end(), fClusters)) { | |
| std::string utf8{&fUTF8[info.utf8Range.begin()], info.utf8Range.size()}; | |
| fJSONWriter->appendString("UTF8", utf8.c_str()); | |
| fJSONWriter->beginArray("glyphs", false); | |
| for (auto glyphID : fGlyphs) { | |
| fJSONWriter->appendU32(glyphID); | |
| } | |
| fJSONWriter->endArray(); | |
| fJSONWriter->beginArray("clusters", false); | |
| for (auto cluster : fClusters) { | |
| fJSONWriter->appendU32(cluster); | |
| } | |
| fJSONWriter->endArray(); | |
| } else { | |
| VisualizeClusters(fUTF8.c_str(), | |
| info.utf8Range.begin(), info.utf8Range.end(), | |
| SkMakeSpan(fGlyphs), | |
| SkMakeSpan(fClusters), | |
| [this](size_t codePointCount, SkSpan<const char> utf1to1, | |
| SkSpan<const SkGlyphID> glyph1to1) { | |
| this->displayMToN(codePointCount, utf1to1, glyph1to1); | |
| }); | |
| } | |
| if (info.glyphCount > 1) { | |
| fJSONWriter->beginArray("horizontal positions", false); | |
| for (auto position : fPositions) { | |
| fJSONWriter->appendFloat(position.x()); | |
| } | |
| fJSONWriter->endArray(); | |
| } | |
| fJSONWriter->beginArray("advances", false); | |
| for (size_t i = 1; i < info.glyphCount; i++) { | |
| fJSONWriter->appendFloat(fPositions[i].fX - fPositions[i-1].fX); | |
| } | |
| SkPoint lastAdvance = info.fAdvance - (fPositions.back() - fPositions.front()); | |
| fJSONWriter->appendFloat(lastAdvance.fX); | |
| fJSONWriter->endArray(); | |
| fJSONWriter->endObject(); | |
| } | |
| void SkShaperJSONWriter::BreakupClusters(size_t utf8Begin, size_t utf8End, | |
| SkSpan<const uint32_t> clusters, | |
| const BreakupCluastersCallback& processMToN) { | |
| if (clusters.front() <= clusters.back()) { | |
| // Handle left-to-right text direction | |
| size_t glyphStartIndex = 0; | |
| for (size_t glyphEndIndex = 0; glyphEndIndex < clusters.size(); glyphEndIndex++) { | |
| if (clusters[glyphStartIndex] == clusters[glyphEndIndex]) { continue; } | |
| processMToN(glyphStartIndex, glyphEndIndex, | |
| clusters[glyphStartIndex], clusters[glyphEndIndex]); | |
| glyphStartIndex = glyphEndIndex; | |
| } | |
| processMToN(glyphStartIndex, clusters.size(), clusters[glyphStartIndex], utf8End); | |
| } else { | |
| // Handle right-to-left text direction. | |
| SkASSERT(clusters.size() >= 2); | |
| size_t glyphStartIndex = 0; | |
| uint32_t utf8EndIndex = utf8End; | |
| for (size_t glyphEndIndex = 0; glyphEndIndex < clusters.size(); glyphEndIndex++) { | |
| if (clusters[glyphStartIndex] == clusters[glyphEndIndex]) { continue; } | |
| processMToN(glyphStartIndex, glyphEndIndex, | |
| clusters[glyphStartIndex], utf8EndIndex); | |
| utf8EndIndex = clusters[glyphStartIndex]; | |
| glyphStartIndex = glyphEndIndex; | |
| } | |
| processMToN(glyphStartIndex, clusters.size(), utf8Begin, clusters[glyphStartIndex-1]); | |
| } | |
| } | |
| void SkShaperJSONWriter::VisualizeClusters(const char* utf8, size_t utf8Begin, size_t utf8End, | |
| SkSpan<const SkGlyphID> glyphIDs, | |
| SkSpan<const uint32_t> clusters, | |
| const VisualizeClustersCallback& processMToN) { | |
| size_t glyphRangeStart, glyphRangeEnd; | |
| uint32_t utf8RangeStart, utf8RangeEnd; | |
| auto resetRanges = [&]() { | |
| glyphRangeStart = std::numeric_limits<size_t>::max(); | |
| glyphRangeEnd = 0; | |
| utf8RangeStart = std::numeric_limits<uint32_t>::max(); | |
| utf8RangeEnd = 0; | |
| }; | |
| auto checkRangesAndProcess = [&]() { | |
| if (glyphRangeStart < glyphRangeEnd) { | |
| size_t glyphRangeCount = glyphRangeEnd - glyphRangeStart; | |
| SkSpan<const char> utf8Span{&utf8[utf8RangeStart], utf8RangeEnd - utf8RangeStart}; | |
| SkSpan<const SkGlyphID> glyphSpan{&glyphIDs[glyphRangeStart], glyphRangeCount}; | |
| // Glyph count is the same as codepoint count for 1:1. | |
| processMToN(glyphRangeCount, utf8Span, glyphSpan); | |
| } | |
| resetRanges(); | |
| }; | |
| auto gatherRuns = [&](size_t glyphStartIndex, size_t glyphEndIndex, | |
| uint32_t utf8StartIndex, uint32_t utf8EndIndex) { | |
| int possibleCount = SkUTF::CountUTF8(&utf8[utf8StartIndex], utf8EndIndex - utf8StartIndex); | |
| if (possibleCount == -1) { return; } | |
| size_t codePointCount = SkTo<size_t>(possibleCount); | |
| if (codePointCount == 1 && glyphEndIndex - glyphStartIndex == 1) { | |
| glyphRangeStart = std::min(glyphRangeStart, glyphStartIndex); | |
| glyphRangeEnd = std::max(glyphRangeEnd, glyphEndIndex ); | |
| utf8RangeStart = std::min(utf8RangeStart, utf8StartIndex ); | |
| utf8RangeEnd = std::max(utf8RangeEnd, utf8EndIndex ); | |
| } else { | |
| checkRangesAndProcess(); | |
| SkSpan<const char> utf8Span{&utf8[utf8StartIndex], utf8EndIndex - utf8StartIndex}; | |
| SkSpan<const SkGlyphID> glyphSpan{&glyphIDs[glyphStartIndex], | |
| glyphEndIndex - glyphStartIndex}; | |
| processMToN(codePointCount, utf8Span, glyphSpan); | |
| } | |
| }; | |
| resetRanges(); | |
| BreakupClusters(utf8Begin, utf8End, clusters, gatherRuns); | |
| checkRangesAndProcess(); | |
| } | |
| void SkShaperJSONWriter::displayMToN(size_t codePointCount, | |
| SkSpan<const char> utf8, | |
| SkSpan<const SkGlyphID> glyphIDs) { | |
| std::string nString = std::to_string(codePointCount); | |
| std::string mString = std::to_string(glyphIDs.size()); | |
| std::string clusterName = "cluster " + nString + " to " + mString; | |
| fJSONWriter->beginObject(clusterName.c_str(), true); | |
| std::string utf8String{utf8.data(), utf8.size()}; | |
| fJSONWriter->appendString("UTF", utf8String.c_str()); | |
| fJSONWriter->beginArray("glyphsIDs", false); | |
| for (auto glyphID : glyphIDs) { | |
| fJSONWriter->appendU32(glyphID); | |
| } | |
| fJSONWriter->endArray(); | |
| fJSONWriter->endObject(); | |
| } |