limit bookmaker status output

streamline 'working as intended' output
notify when output changed
fix bug which appended output incorrectly to SkBitmap.h
fix bug that hid bad SkSurface.h from detection

Docs-Preview: https://skia.org/?cl=90162
Bug: skia:6898
Change-Id: I067cfe5bbad706345fb5cd540cdc3835ce22d822
Reviewed-on: https://skia-review.googlesource.com/90162
Commit-Queue: Cary Clark <caryclark@skia.org>
Reviewed-by: Cary Clark <caryclark@skia.org>
diff --git a/tools/bookmaker/bookmaker.cpp b/tools/bookmaker/bookmaker.cpp
index b7aa044..eb1ce1e 100644
--- a/tools/bookmaker/bookmaker.cpp
+++ b/tools/bookmaker/bookmaker.cpp
@@ -16,7 +16,7 @@
 DEFINE_bool2(hack, k, false, "Do a find/replace hack to update all *.bmh files. (Requires -b)");
 DEFINE_bool2(stdout, o, false, "Write file out to standard out.");
 DEFINE_bool2(populate, p, false, "Populate include from bmh. (Requires -b -i)");
-DEFINE_string2(ref, r, "", "Resolve refs and write bmh_*.md files to path. (Requires -b -f)");
+DEFINE_string2(ref, r, "", "Resolve refs and write *.md files to path. (Requires -b -f)");
 DEFINE_string2(spellcheck, s, "", "Spell-check [once, all, mispelling]. (Requires -b)");
 DEFINE_bool2(tokens, t, false, "Write bmh from include. (Requires -b -i)");
 DEFINE_bool2(crosscheck, x, false, "Check bmh against includes. (Requires -b -i)");
diff --git a/tools/bookmaker/bookmaker.h b/tools/bookmaker/bookmaker.h
index f309d79..792f7d9 100644
--- a/tools/bookmaker/bookmaker.h
+++ b/tools/bookmaker/bookmaker.h
@@ -1041,6 +1041,8 @@
         fParent = fParent->fParent;
     }
 
+    const char* ReadToBuffer(string filename, int* size);
+
     virtual void reset() = 0;
 
     void resetCommon() {
@@ -1104,6 +1106,7 @@
         this->writeString(str.c_str());
     }
 
+    bool writtenFileDiffers(string filename, string readname);
 
     unordered_map<string, sk_sp<SkData>> fRawData;
     unordered_map<string, vector<char>> fLFOnly;
@@ -1195,7 +1198,7 @@
 // names without formal definitions (e.g. Column) aren't included
 // fill in other names once they're actually used
   { "",            nullptr,      MarkType::kNone,         R_Y, E_N, 0 }
-, { "A",           nullptr,      MarkType::kAnchor,       R_Y, E_N, 0 }
+, { "A",           nullptr,      MarkType::kAnchor,       R_N, E_N, 0 }
 , { "Alias",       nullptr,      MarkType::kAlias,        R_N, E_N, 0 }
 , { "Bug",         nullptr,      MarkType::kBug,          R_N, E_N, 0 }
 , { "Class",       &fClassMap,   MarkType::kClass,        R_Y, E_O, M_CSST | M(Root) }
@@ -1556,6 +1559,7 @@
         fInFunction = false;
         fInString = false;
         fFailed = false;
+        fPriorEnum = nullptr;
     }
 
     void setBracketShortCuts(Bracket bracket) {
@@ -1724,6 +1728,8 @@
     Definition* fRootTopic;
     Definition* fInBrace;
     Definition* fLastObject;
+    Definition* fPriorEnum;
+    int fPriorIndex;
     const char* fIncludeWord;
     char fPrev;
     bool fInChar;
@@ -1837,7 +1843,6 @@
     void structOut(const Definition* root, const Definition& child,
             const char* commentStart, const char* commentEnd);
     void structSizeMembers(const Definition& child);
-
 private:
     BmhParser* fBmhParser;
     Definition* fDeferComment;
@@ -1966,8 +1971,8 @@
         this->reset();
     }
 
-    bool buildReferences(const char* path, const char* outDir);
-    bool buildStatus(const char* path, const char* outDir);
+    bool buildReferences(const char* docDir, const char* mdOutDirOrFile);
+    bool buildStatus(const char* docDir, const char* mdOutDir);
 private:
     enum class TableState {
         kNone,
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp
index 363aacc..fa5ad32 100644
--- a/tools/bookmaker/includeParser.cpp
+++ b/tools/bookmaker/includeParser.cpp
@@ -449,6 +449,8 @@
             }
         }
     }
+    int crossChecks = 0;
+    string firstCheck;
     for (auto& classMapper : fIClassMap) {
         string className = classMapper.first;
         auto finder = bmhParser.fClassMap.find(className);
@@ -460,7 +462,19 @@
             SkDebugf("some struct elements not found; struct finding in includeParser is missing\n");
             fFailed = true;
         }
-        SkDebugf("cross-checked %s\n", className.c_str());
+        if (crossChecks) {
+            SkDebugf(".");
+        } else {
+            SkDebugf("cross-check");
+            firstCheck = className;
+        }
+        ++crossChecks;
+    }
+    if (crossChecks) {
+        if (1 == crossChecks) {
+            SkDebugf("%s", firstCheck.c_str());
+        }
+        SkDebugf("\n");
     }
     bmhParser.fWroteOut = true;
     return !fFailed;
@@ -1338,22 +1352,21 @@
         }
         markupChild->fChildren.push_back(member);
     } while (true);
-    for (auto count : child->fChildren) {
-        if (Definition::Type::kBracket == count->fType) {
+    for (auto outsideMember : child->fChildren) {
+        if (Definition::Type::kBracket == outsideMember->fType) {
             continue;
         }
-        SkASSERT(Definition::Type::kKeyWord == count->fType);
-        if (KeyWord::kClass == count->fKeyWord) {
+        SkASSERT(Definition::Type::kKeyWord == outsideMember->fType);
+        if (KeyWord::kClass == outsideMember->fKeyWord) {
             continue;
         }
-        SkASSERT(KeyWord::kStatic == count->fKeyWord);
-        markupChild->fTokens.emplace_back(MarkType::kMember, count->fContentStart,
-                count->fContentEnd, count->fLineCount, markupChild);
+        SkASSERT(KeyWord::kStatic == outsideMember->fKeyWord);
+        markupChild->fTokens.emplace_back(MarkType::kMember, outsideMember->fContentStart,
+                outsideMember->fContentEnd, outsideMember->fLineCount, markupChild);
         Definition* member = &markupChild->fTokens.back();
-        member->fName = count->fName;
+        member->fName = outsideMember->fName;
         // FIXME: ? add comment as well ?
         markupChild->fChildren.push_back(member);
-        break;
     }
     IClassDefinition& classDef = fIClassMap[markupDef->fName];
     SkASSERT(classDef.fStart);
@@ -1493,9 +1506,6 @@
     }
     tokenIter->fName = nameStr;
     tokenIter->fMarkType = MarkType::kMethod;
-    if (string::npos != nameStr.find("defined")) {
-        SkDebugf("");
-    }
     tokenIter->fPrivate = string::npos != nameStr.find("::");
     auto testIter = child->fParent->fTokens.begin();
     SkASSERT(child->fParentIndex > 0);
@@ -2036,6 +2046,7 @@
                     fInEnum = false;
                 }
                 this->popObject();
+                fPriorEnum = nullptr;
             } else if (Definition::Type::kBracket == fParent->fType
                     && fParent->fParent && Definition::Type::kKeyWord == fParent->fParent->fType
                     && KeyWord::kStruct == fParent->fParent->fKeyWord) {
@@ -2069,27 +2080,31 @@
                     for (auto nameType = baseIter; nameType != namedIter; ++nameType) {
                         member->fChildren.push_back(&*nameType);
                     }
-
                 }
+                fPriorEnum = nullptr;
             } else if (fParent->fChildren.size() > 0) {
                 auto lastIter = fParent->fChildren.end();
-                Definition* priorEnum;
-                while (fParent->fChildren.begin() != lastIter) {
-                    std::advance(lastIter, -1);
-                    priorEnum = *lastIter;
-                    if (Definition::Type::kBracket != priorEnum->fType ||
-                            (Bracket::kSlashSlash != priorEnum->fBracket
-                            && Bracket::kSlashStar != priorEnum->fBracket)) {
-                        break;
+                Definition* priorEnum = fPriorEnum;
+                fPriorEnum = nullptr;
+                if (!priorEnum) {
+                    while (fParent->fChildren.begin() != lastIter) {
+                        std::advance(lastIter, -1);
+                        priorEnum = *lastIter;
+                        if (Definition::Type::kBracket != priorEnum->fType ||
+                                (Bracket::kSlashSlash != priorEnum->fBracket
+                                && Bracket::kSlashStar != priorEnum->fBracket)) {
+                            break;
+                        }
                     }
+                    fPriorIndex = priorEnum->fParentIndex;
                 }
                 if (Definition::Type::kKeyWord == priorEnum->fType
                         && KeyWord::kEnum == priorEnum->fKeyWord) {
                     auto tokenWalker = fParent->fTokens.begin();
-                    std::advance(tokenWalker, priorEnum->fParentIndex);
-                    SkASSERT(KeyWord::kEnum == tokenWalker->fKeyWord);
+                    std::advance(tokenWalker, fPriorIndex);
                     while (tokenWalker != fParent->fTokens.end()) {
                         std::advance(tokenWalker, 1);
+                        ++fPriorIndex;
                         if (Punctuation::kSemicolon == tokenWalker->fPunctuation) {
                             break;
                         }
@@ -2103,6 +2118,7 @@
                             break;
                         }
                     }
+                    auto saveTokenWalker = tokenWalker;
                     Definition* start = &*tokenWalker;
                     bool foundExpected = true;
                     for (KeyWord expected : {KeyWord::kStatic, KeyWord::kConstExpr, KeyWord::kInt}){
@@ -2116,6 +2132,36 @@
                         }
                         std::advance(tokenWalker, 1);
                     }
+                    if (!foundExpected) {
+                        foundExpected = true;
+                        tokenWalker = saveTokenWalker;
+                        for (KeyWord expected : {KeyWord::kStatic, KeyWord::kConst, KeyWord::kNone}){
+                            const Definition* test = &*tokenWalker;
+                            if (expected != test->fKeyWord) {
+                                foundExpected = false;
+                                break;
+                            }
+                            if (tokenWalker == fParent->fTokens.end()) {
+                                break;
+                            }
+                            if (KeyWord::kNone != expected) {
+                                std::advance(tokenWalker, 1);
+                            }
+                        }
+                        if (foundExpected) {
+                            auto nameToken = priorEnum->fTokens.begin();
+                            string enumName = string(nameToken->fContentStart,
+                                    nameToken->fContentEnd - nameToken->fContentStart);
+                            const Definition* test = &*tokenWalker;
+                            string constType = string(test->fContentStart,
+                                    test->fContentEnd - test->fContentStart);
+                            if (enumName != constType) {
+                                foundExpected = false;
+                            } else {
+                                std::advance(tokenWalker, 1);
+                            }
+                        }
+                    }
                     if (foundExpected && tokenWalker != fParent->fTokens.end()) {
                         const char* nameStart = tokenWalker->fStart;
                         std::advance(tokenWalker, 1);
@@ -2125,7 +2171,8 @@
                             start->fName = string(nameStart, tp.fChar - nameStart);
                             start->fContentEnd = fChar;
                             priorEnum->fChildren.emplace_back(start);
-                       }
+                            fPriorEnum = priorEnum;
+                        }
                     }
                 }
             }
diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp
index c34fc93..463ac92 100644
--- a/tools/bookmaker/includeWriter.cpp
+++ b/tools/bookmaker/includeWriter.cpp
@@ -486,7 +486,7 @@
     int longestValue = 0;
     int valueLen = 0;
     const char* lastEnd = nullptr;
-    SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
+//    SkASSERT(child.fChildren.size() == 1 || child.fChildren.size() == 2);
     auto brace = child.fChildren[0];
     if (KeyWord::kClass == brace->fKeyWord) {
         brace = brace->fChildren[0];
@@ -585,6 +585,9 @@
 
 // walk children and output complete method doxygen description
 void IncludeWriter::methodOut(const Definition* method, const Definition& child) {
+    if (string::npos != method->fName.find("validate")) {
+        SkDebugf("");
+    }
     if (fPendingMethod) {
         fIndent -= 4;
         fPendingMethod = false;
@@ -1076,12 +1079,22 @@
             const char* bodyEnd = fDeferComment ? fDeferComment->fContentStart - 1 :
                     fAttrDeprecated ? fAttrDeprecated->fContentStart - 1 :
                     child.fContentStart;
+            if (Definition::Type::kBracket == def->fType && Bracket::kDebugCode == def->fBracket) {
+                auto tokenIter = def->fParent->fTokens.begin();
+                std::advance(tokenIter, def->fParentIndex - 1);
+                Definition* prior = &*tokenIter;
+                if (Definition::Type::kBracket == def->fType &&
+                        Bracket::kSlashStar == prior->fBracket) {
+                    bodyEnd = prior->fContentStart - 1;
+                }
+            }
             // FIXME: roll end-trimming into writeBlockTrim call
             while (fStart < bodyEnd && ' ' >= bodyEnd[-1]) {
                 --bodyEnd;
             }
             int blockSize = (int) (bodyEnd - fStart);
             if (blockSize) {
+                string debugstr(fStart, blockSize);
                 this->writeBlock(blockSize, fStart);
             }
             startDef = &child;
@@ -1445,7 +1458,22 @@
         this->lfcr();
         this->writePending();
         fclose(fOut);
-        SkDebugf("wrote %s\n", fileName.c_str());
+        fflush(fOut);
+        size_t slash = fFileName.find_last_of('/');
+        if (string::npos == slash) {
+            slash = 0;
+        }
+        size_t back = fFileName.find_last_of('\\');
+        if (string::npos == back) {
+            back = 0;
+        }
+        string dir = fFileName.substr(0, SkTMax(slash, back) + 1);
+        string readname = dir + fileName;
+        if (this->writtenFileDiffers(fileName, readname)) {
+            SkDebugf("wrote updated %s\n", fileName.c_str());
+        } else {
+            remove(fileName.c_str());
+        }
     }
     return allPassed;
 }
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp
index d6069e4..0443199 100644
--- a/tools/bookmaker/mdOut.cpp
+++ b/tools/bookmaker/mdOut.cpp
@@ -10,6 +10,12 @@
 #include "SkOSFile.h"
 #include "SkOSPath.h"
 
+#define FPRINTF(...)                \
+    if (fDebugOut) {                \
+        SkDebugf(__VA_ARGS__);      \
+    }                               \
+    fprintf(fOut, __VA_ARGS__)
+
 static void add_ref(const string& leadingSpaces, const string& ref, string* result) {
     *result += leadingSpaces + ref;
 }
@@ -65,7 +71,11 @@
         }
         t.skipToMethodEnd();
         if (base == t.fChar) {
-            break;
+            if (!t.eof() && '~' == base[0] && !isalnum(base[1])) {
+                t.next();
+            } else {
+                break;
+            }
         }
         if (start >= t.fChar) {
             continue;
@@ -76,6 +86,10 @@
         ref = string(start, t.fChar - start);
         if (const Definition* def = this->isDefined(t, ref,
                 BmhParser::Resolvable::kOut != resolvable)) {
+            if (MarkType::kExternal == def->fMarkType) {
+                add_ref(leadingSpaces, ref, &result);
+                continue;
+            }
             SkASSERT(def->fFiddle.length());
             if (!t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) {
                 if (!t.skipToEndBracket(')')) {
@@ -237,16 +251,21 @@
     return result;
 }
 
-bool MdOut::buildReferences(const char* fileOrPath, const char* outDir) {
-    if (!sk_isdir(fileOrPath)) {
-        if (!this->buildRefFromFile(fileOrPath, outDir)) {
-            SkDebugf("failed to parse %s\n", fileOrPath);
+bool MdOut::buildReferences(const char* docDir, const char* mdFileOrPath) {
+    if (!sk_isdir(mdFileOrPath)) {
+        SkString mdFile = SkOSPath::Basename(mdFileOrPath);
+        SkString bmhFile = SkOSPath::Join(docDir, mdFile.c_str());
+        bmhFile.remove(bmhFile.size() - 3, 3);
+        bmhFile += ".bmh";
+        SkString mdPath = SkOSPath::Dirname(mdFileOrPath);
+        if (!this->buildRefFromFile(bmhFile.c_str(), mdPath.c_str())) {
+            SkDebugf("failed to parse %s\n", mdFileOrPath);
             return false;
         }
     } else {
-        SkOSFile::Iter it(fileOrPath, ".bmh");
+        SkOSFile::Iter it(docDir, ".bmh");
         for (SkString file; it.next(&file); ) {
-            SkString p = SkOSPath::Join(fileOrPath, file.c_str());
+            SkString p = SkOSPath::Join(docDir, file.c_str());
             const char* hunk = p.c_str();
             if (!SkStrEndsWith(hunk, ".bmh")) {
                 continue;
@@ -254,7 +273,7 @@
             if (SkStrEndsWith(hunk, "markup.bmh")) {  // don't look inside this for now
                 continue;
             }
-            if (!this->buildRefFromFile(hunk, outDir)) {
+            if (!this->buildRefFromFile(hunk, mdFileOrPath)) {
                 SkDebugf("failed to parse %s\n", hunk);
                 return false;
             }
@@ -318,7 +337,7 @@
                 fullName += '/';
             }
             fullName += filename;
-            fOut = fopen(fullName.c_str(), "wb");
+            fOut = fopen(filename.c_str(), "wb");
             if (!fOut) {
                 SkDebugf("could not open output file %s\n", fullName.c_str());
                 return false;
@@ -328,16 +347,26 @@
                 header.replace(underscorePos, 1, " ");
             }
             SkASSERT(string::npos == header.find('_'));
-            fprintf(fOut, "%s", header.c_str());
+            FPRINTF("%s", header.c_str());
             this->lfAlways(1);
-            fprintf(fOut, "===");
+            FPRINTF("===");
         }
         this->markTypeOut(topicDef);
     }
     if (fOut) {
         this->writePending();
         fclose(fOut);
-        SkDebugf("wrote %s\n", fullName.c_str());
+        fflush(fOut);
+        if (this->writtenFileDiffers(filename, fullName)) {
+            fOut = fopen(fullName.c_str(), "wb");
+            int writtenSize;
+            const char* written = ReadToBuffer(filename, &writtenSize);
+            fwrite(written, 1, writtenSize, fOut);
+            fclose(fOut);
+            fflush(fOut);
+            SkDebugf("wrote updated %s\n", fullName.c_str());
+        }
+        remove(filename.c_str());
         fOut = nullptr;
     }
     return true;
@@ -642,35 +671,46 @@
             (!def->fParent || MarkType::kConst != def->fParent->fMarkType) &&
             TableState::kNone != fTableState) {
         this->writePending();
-        fprintf(fOut, "</table>");
+        FPRINTF("</table>");
         this->lf(2);
         fTableState = TableState::kNone;
     }
     switch (def->fMarkType) {
         case MarkType::kAlias:
             break;
-        case MarkType::kAnchor:
-            break;
+        case MarkType::kAnchor: {
+            if (fColumn > 0) {
+                this->writeSpace();
+            }
+            this->writePending();
+            TextParser parser(def);
+            const char* start = parser.fChar;
+            parser.skipToEndBracket(" # ");
+            string anchorText(start, parser.fChar - start);
+            parser.skipExact(" # ");
+            string anchorLink(parser.fChar, parser.fEnd - parser.fChar);
+            FPRINTF("<a href=\"%s\">%s", anchorLink.c_str(), anchorText.c_str());
+            } break;
         case MarkType::kBug:
             break;
         case MarkType::kClass:
             this->mdHeaderOut(1);
-            fprintf(fOut, "<a name=\"%s\"></a> Class %s", this->linkName(def).c_str(),
+            FPRINTF("<a name=\"%s\"></a> Class %s", this->linkName(def).c_str(),
                     def->fName.c_str());
             this->lf(1);
             break;
         case MarkType::kCode:
             this->lfAlways(2);
-            fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em;"
+            FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;"
                     "width: 62.5em; background-color: #f0f0f0\">");
             this->lf(1);
             break;
         case MarkType::kColumn:
             this->writePending();
             if (fInList) {
-                fprintf(fOut, "    <td>");
+                FPRINTF("    <td>");
             } else {
-                fprintf(fOut, "| ");
+                FPRINTF("| ");
             }
             break;
         case MarkType::kComment:
@@ -678,7 +718,7 @@
         case MarkType::kConst: {
             if (TableState::kNone == fTableState) {
                 this->mdHeaderOut(3);
-                fprintf(fOut, "Constants\n"
+                FPRINTF("Constants\n"
                         "\n"
                         "<table>");
                 fTableState = TableState::kRow;
@@ -686,19 +726,19 @@
             }
             if (TableState::kRow == fTableState) {
                 this->writePending();
-                fprintf(fOut, "  <tr>");
+                FPRINTF("  <tr>");
                 this->lf(1);
                 fTableState = TableState::kColumn;
             }
             this->writePending();
-            fprintf(fOut, "    <td><a name=\"%s\"> <code><strong>%s </strong></code> </a></td>",
+            FPRINTF("    <td><a name=\"%s\"> <code><strong>%s </strong></code> </a></td>",
                     def->fFiddle.c_str(), def->fName.c_str());
             const char* lineEnd = strchr(textStart, '\n');
             SkASSERT(lineEnd < def->fTerminator);
             SkASSERT(lineEnd > textStart);
             SkASSERT((int) (lineEnd - textStart) == lineEnd - textStart);
-            fprintf(fOut, "<td>%.*s</td>", (int) (lineEnd - textStart), textStart);
-            fprintf(fOut, "<td>");
+            FPRINTF("<td>%.*s</td>", (int) (lineEnd - textStart), textStart);
+            FPRINTF("<td>");
             textStart = lineEnd;
         } break;
         case MarkType::kDefine:
@@ -710,21 +750,21 @@
         case MarkType::kDescription:
             fInDescription = true;
             this->writePending();
-            fprintf(fOut, "<div>");
+            FPRINTF("<div>");
             break;
         case MarkType::kDoxygen:
             break;
         case MarkType::kEnum:
         case MarkType::kEnumClass:
             this->mdHeaderOut(2);
-            fprintf(fOut, "<a name=\"%s\"></a> Enum %s", def->fFiddle.c_str(), def->fName.c_str());
+            FPRINTF("<a name=\"%s\"></a> Enum %s", def->fFiddle.c_str(), def->fName.c_str());
             this->lf(2);
             break;
         case MarkType::kError:
             break;
         case MarkType::kExample: {
             this->mdHeaderOut(3);
-            fprintf(fOut, "Example\n"
+            FPRINTF("Example\n"
                             "\n");
             fHasFiddle = true;
             bool showGpu = false;
@@ -740,21 +780,21 @@
             }
             if (fHasFiddle && !def->hasChild(MarkType::kError)) {
                 SkASSERT(def->fHash.length() > 0);
-                fprintf(fOut, "<div><fiddle-embed name=\"%s\"", def->fHash.c_str());
+                FPRINTF("<div><fiddle-embed name=\"%s\"", def->fHash.c_str());
                 if (showGpu) {
-                    fprintf(fOut, " gpu=\"true\"");
+                    FPRINTF(" gpu=\"true\"");
                     if (gpuAndCpu) {
-                        fprintf(fOut, " cpu=\"true\"");
+                        FPRINTF(" cpu=\"true\"");
                     }
                 }
-                fprintf(fOut, ">");
+                FPRINTF(">");
             } else {
                 SkASSERT(def->fHash.length() == 0);
-                fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px"
+                FPRINTF("<pre style=\"padding: 1em 1em 1em 1em; font-size: 13px"
                         " width: 62.5em; background-color: #f0f0f0\">");
                 this->lfAlways(1);
                 if (def->fWrapper.length() > 0) {
-                    fprintf(fOut, "%s", def->fWrapper.c_str());
+                    FPRINTF("%s", def->fWrapper.c_str());
                 }
                 fRespectLeadingSpace = true;
             }
@@ -780,7 +820,7 @@
         case MarkType::kList:
             fInList = true;
             this->lfAlways(2);
-            fprintf(fOut, "<table>");
+            FPRINTF("<table>");
             this->lf(1);
             break;
         case MarkType::kLiteral:
@@ -794,7 +834,7 @@
             tp.skipWhiteSpace();
             const char* end = tp.trimmedBracketEnd('\n');
             this->lfAlways(2);
-            fprintf(fOut, "<a name=\"%s\"> <code><strong>%.*s</strong></code> </a>",
+            FPRINTF("<a name=\"%s\"> <code><strong>%.*s</strong></code> </a>",
                     def->fFiddle.c_str(), (int) (end - tp.fChar), tp.fChar);
             this->lf(2);
             } break;
@@ -804,9 +844,9 @@
 
             if (!def->isClone()) {
                 this->lfAlways(2);
-                fprintf(fOut, "<a name=\"%s\"></a>", def->fFiddle.c_str());
+                FPRINTF("<a name=\"%s\"></a>", def->fFiddle.c_str());
                 this->mdHeaderOutLF(2, 1);
-                fprintf(fOut, "%s", method_name.c_str());
+                FPRINTF("%s", method_name.c_str());
                 this->lf(2);
             }
 
@@ -814,7 +854,7 @@
             // TODO: 50em below should match limit = 80 in formatFunction()
             this->writePending();
             string preformattedStr = preformat(formattedStr);
-            fprintf(fOut, "<pre style=\"padding: 1em 1em 1em 1em;"
+            FPRINTF("<pre style=\"padding: 1em 1em 1em 1em;"
                                     "width: 62.5em; background-color: #f0f0f0\">\n"
                             "%s\n"
                             "</pre>",  preformattedStr.c_str());
@@ -838,7 +878,7 @@
                 fTableState = TableState::kRow;
             }
             if (TableState::kRow == fTableState) {
-                fprintf(fOut, "  <tr>");
+                FPRINTF("  <tr>");
                 this->lf(1);
                 fTableState = TableState::kColumn;
             }
@@ -866,7 +906,7 @@
             break;
         case MarkType::kReturn:
             this->mdHeaderOut(3);
-            fprintf(fOut, "Return Value");
+            FPRINTF("Return Value");
             if (!this->checkParamReturnBody(def)) {
                 return;
             }
@@ -874,13 +914,13 @@
             break;
         case MarkType::kRow:
             if (fInList) {
-                fprintf(fOut, "  <tr>");
+                FPRINTF("  <tr>");
                 this->lf(1);
             }
             break;
         case MarkType::kSeeAlso:
             this->mdHeaderOut(3);
-            fprintf(fOut, "See Also");
+            FPRINTF("See Also");
             this->lf(2);
             break;
         case MarkType::kSet:
@@ -896,23 +936,23 @@
             code.skipSpace();
             while (!code.eof()) {
                 const char* end = code.trimmedLineEnd();
-                fprintf(fOut, "%.*s\n", (int) (end - code.fChar), code.fChar);
+                FPRINTF("%.*s\n", (int) (end - code.fChar), code.fChar);
                 code.skipToLineStart();
             }
-            fprintf(fOut, "~~~~");
+            FPRINTF("~~~~");
             this->lf(2);
             } break;
         case MarkType::kStruct:
             fRoot = def->asRoot();
             this->mdHeaderOut(1);
-            fprintf(fOut, "<a name=\"%s\"></a> Struct %s", def->fFiddle.c_str(), def->fName.c_str());
+            FPRINTF("<a name=\"%s\"></a> Struct %s", def->fFiddle.c_str(), def->fName.c_str());
             this->lf(1);
             break;
         case MarkType::kSubstitute:
             break;
         case MarkType::kSubtopic:
             this->mdHeaderOut(2);
-            fprintf(fOut, "<a name=\"%s\"></a> %s", def->fName.c_str(), printable.c_str());
+            FPRINTF("<a name=\"%s\"></a> %s", def->fName.c_str(), printable.c_str());
             this->lf(2);
             break;
         case MarkType::kTable:
@@ -928,7 +968,7 @@
             break;
         case MarkType::kTopic:
             this->mdHeaderOut(1);
-            fprintf(fOut, "<a name=\"%s\"></a> %s", this->linkName(def).c_str(),
+            FPRINTF("<a name=\"%s\"></a> %s", this->linkName(def).c_str(),
                     printable.c_str());
             this->lf(1);
             break;
@@ -951,23 +991,28 @@
     }
     this->childrenOut(def, textStart);
     switch (def->fMarkType) {  // post child work, at least for tables
+        case MarkType::kAnchor:
+            if (fColumn > 0) {
+                this->writeSpace();
+            }
+            break;
         case MarkType::kCode:
             this->writePending();
-            fprintf(fOut, "</pre>");
+            FPRINTF("</pre>");
             this->lf(2);
             break;
         case MarkType::kColumn:
             if (fInList) {
                 this->writePending();
-                fprintf(fOut, "</td>");
+                FPRINTF("</td>");
                 this->lf(1);
             } else {
-                fprintf(fOut, " ");
+                FPRINTF(" ");
             }
             break;
         case MarkType::kDescription:
             this->writePending();
-            fprintf(fOut, "</div>");
+            FPRINTF("</div>");
             fInDescription = false;
             break;
         case MarkType::kEnum:
@@ -977,22 +1022,26 @@
         case MarkType::kExample:
             this->writePending();
             if (fHasFiddle) {
-                fprintf(fOut, "</fiddle-embed></div>");
+                FPRINTF("</fiddle-embed></div>");
             } else {
                 this->lfAlways(1);
                 if (def->fWrapper.length() > 0) {
-                    fprintf(fOut, "}");
+                    FPRINTF("}");
                     this->lfAlways(1);
                 }
-                fprintf(fOut, "</pre>");
+                FPRINTF("</pre>");
             }
             this->lf(2);
             fRespectLeadingSpace = false;
             break;
+        case MarkType::kLink:
+            this->writeString("</a>");
+            this->writeSpace();
+            break;
         case MarkType::kList:
             fInList = false;
             this->writePending();
-            fprintf(fOut, "</table>");
+            FPRINTF("</table>");
             this->lf(2);
             break;
         case MarkType::kLegend: {
@@ -1003,15 +1052,15 @@
             SkASSERT(columnCount > 0);
             this->writePending();
             for (size_t index = 0; index < columnCount; ++index) {
-                fprintf(fOut, "| --- ");
+                FPRINTF("| --- ");
             }
-            fprintf(fOut, " |");
+            FPRINTF(" |");
             this->lf(1);
             } break;
         case MarkType::kMethod:
             fMethod = nullptr;
             this->lfAlways(2);
-            fprintf(fOut, "---");
+            FPRINTF("---");
             this->lf(2);
             break;
         case MarkType::kConst:
@@ -1019,8 +1068,8 @@
             SkASSERT(TableState::kColumn == fTableState);
             fTableState = TableState::kRow;
             this->writePending();
-            fprintf(fOut, "</td>\n");
-            fprintf(fOut, "  </tr>");
+            FPRINTF("</td>\n");
+            FPRINTF("  </tr>");
             this->lf(1);
             break;
         case MarkType::kReturn:
@@ -1029,9 +1078,9 @@
             break;
         case MarkType::kRow:
             if (fInList) {
-                fprintf(fOut, "  </tr>");
+                FPRINTF("  </tr>");
             } else {
-                fprintf(fOut, "|");
+                FPRINTF("|");
             }
             this->lf(1);
             break;
@@ -1051,9 +1100,9 @@
 void MdOut::mdHeaderOutLF(int depth, int lf) {
     this->lfAlways(lf);
     for (int index = 0; index < depth; ++index) {
-        fprintf(fOut, "#");
+        FPRINTF("#");
     }
-    fprintf(fOut, " ");
+    FPRINTF(" ");
 }
 
 void MdOut::resolveOut(const char* start, const char* end, BmhParser::Resolvable resolvable) {
@@ -1098,22 +1147,28 @@
             paragraph.skipToEndBracket('\n');
             ptrdiff_t lineLength = paragraph.fChar - contentStart;
             if (lineLength) {
-                this->writePending();
-                fprintf(fOut, "%.*s", (int) lineLength, contentStart);
+                while (lineLength && contentStart[lineLength - 1] <= ' ') {
+                    --lineLength;
+                }
+                string str(contentStart, lineLength);
+                this->writeString(str.c_str());
             }
+#if 0
             int linefeeds = 0;
             while (lineLength > 0 && '\n' == contentStart[--lineLength]) {
+
                 ++linefeeds;
             }
             if (lineLength > 0) {
                 this->nl();
             }
             fLinefeeds += linefeeds;
+#endif
             if (paragraph.eof()) {
                 break;
             }
             if ('\n' == paragraph.next()) {
-                linefeeds = 1;
+                int linefeeds = 1;
                 if (!paragraph.eof() && '\n' == paragraph.peek()) {
                     linefeeds = 2;
                 }
@@ -1122,7 +1177,7 @@
         }
 #if 0
         while (end > start && end[0] == '\n') {
-            fprintf(fOut, "\n");
+            FPRINTF("\n");
             --end;
         }
 #endif
diff --git a/tools/bookmaker/parserCommon.cpp b/tools/bookmaker/parserCommon.cpp
index af1031a..cfd42f7 100644
--- a/tools/bookmaker/parserCommon.cpp
+++ b/tools/bookmaker/parserCommon.cpp
@@ -224,6 +224,52 @@
     fMaxLF = 2;
 }
 
+const char* ParserCommon::ReadToBuffer(string filename, int* size) {
+    FILE* file = fopen(filename.c_str(), "rb");
+    if (!file) {
+        return nullptr;
+    }
+    fseek(file, 0L, SEEK_END);
+    *size = (int) ftell(file);
+    rewind(file);
+    char* buffer = new char[*size];
+    memset(buffer, ' ', *size);
+    SkAssertResult(*size == (int)fread(buffer, 1, *size, file));
+    fclose(file);
+    fflush(file);
+    return buffer;
+}
+
+bool ParserCommon::writtenFileDiffers(string filename, string readname) {
+    int writtenSize, readSize;
+    const char* written = ReadToBuffer(filename, &writtenSize);
+    if (!written) {
+        return true;
+    }
+    const char* read = ReadToBuffer(readname, &readSize);
+    if (!read) {
+        delete[] written;
+        return true;
+    }
+#if 0  // enable for debugging this
+    int smaller = SkTMin(writtenSize, readSize);
+    for (int index = 0; index < smaller; ++index) {
+        if (written[index] != read[index]) {
+            SkDebugf("%.*s\n", 40, &written[index]);
+            SkDebugf("%.*s\n", 40, &read[index]);
+            break;
+        }
+    }
+#endif
+    if (readSize != writtenSize) {
+        return true;
+    }
+    bool result = !!memcmp(written, read, writtenSize);
+    delete[] written;
+    delete[] read;
+    return result;
+}
+
 StatusIter::StatusIter(const char* statusFile, const char* suffix, StatusFilter filter)
     : fSuffix(suffix)
     , fFilter(filter) {