| /* |
| * 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 "include/core/SkFont.h" |
| #include "include/core/SkSpan.h" |
| #include "include/core/SkString.h" |
| #include "include/core/SkTypeface.h" |
| #include "include/private/SkTo.h" |
| #include "src/utils/SkJSONWriter.h" |
| #include "src/utils/SkUTF.h" |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <limits> |
| #include <string> |
| #include <type_traits> |
| |
| 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); |
| |
| // 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); |
| } |
| |
| 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); |
| |
| 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(), |
| SkSpan(fGlyphs), |
| SkSpan(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); |
| fJSONWriter->beginArray("glyphsIDs", false); |
| for (auto glyphID : glyphIDs) { |
| fJSONWriter->appendU32(glyphID); |
| } |
| fJSONWriter->endArray(); |
| fJSONWriter->endObject(); |
| } |