Check every word in docs

This enables checking all text to see if it
either represents a valid reference or is a
word in docs/spelling.txt . Expressions are
checked for validity as well.

There are a few shortcuts (marked with TODO):
- typedefs aren't resolved, so cheats are
added for SkVector and SkIVector.
- operator overload detection is incomplete
- constructor detection is incomplete
- formula definitions aren't detected

Found and fixed a bunch of spelling, usage,
and incorrect or obsolete references.

A few comment changes are needed in
include/core to get this to work, mostly
centered around recent SkPaint/SkFont edits.

TBR=reed@google.com
Docs-Preview: https://skia.org/?cl=167541
Bug: skia:
Change-Id: I2e0d5990105c5a8482b0c0d3e50fd0b330996dd6
Reviewed-on: https://skia-review.googlesource.com/c/167541
Reviewed-by: Cary Clark <caryclark@skia.org>
Auto-Submit: Cary Clark <caryclark@skia.org>
Commit-Queue: Cary Clark <caryclark@skia.org>
diff --git a/tools/bookmaker/bmhParser.cpp b/tools/bookmaker/bmhParser.cpp
index bfdffc7..a0a46c3 100644
--- a/tools/bookmaker/bmhParser.cpp
+++ b/tools/bookmaker/bmhParser.cpp
@@ -294,6 +294,7 @@
                     definition->fFiddle = parent->fFiddle + '_';
                 }
                 rootDefinition->fNames.fName = rootDefinition->fName;
+                rootDefinition->fNames.fParent = &fGlobalNames;
                 definition->fFiddle += Definition::NormalizedName(typeNameBuilder[0]);
                 this->setAsParent(definition);
             }
@@ -1281,13 +1282,21 @@
     fclose(file);
     int i = 0;
     int start = i;
+    string last = " ";
     string word;
     do {
         if (' ' < buffer[i]) {
             ++i;
             continue;
         }
+        last = word;
         word = string(&buffer[start], i - start);
+#ifdef SK_DEBUG
+        SkASSERT(last.compare(word) < 0);
+        for (char c : word) {
+            SkASSERT(islower(c) || '-' == c);
+        }
+#endif
         if (fGlobalNames.fRefMap.end() == fGlobalNames.fRefMap.find(word)) {
             fGlobalNames.fRefMap[word] = nullptr;
         } else {
@@ -1320,23 +1329,14 @@
                 continue;
             }
         }
-        size_t lastUnder = name.rfind('_');
-        if (string::npos != lastUnder && ++lastUnder < name.length()) {
-            bool numbers = true;
-            for (size_t index = lastUnder; index < name.length(); ++index) {
-                numbers &= (bool) isdigit(name[index]);
-            }
-            if (numbers) {
-                continue;
-            }
-        }
         string ref;
         if (&fGlobalNames == names) {
             ref = ParserCommon::HtmlFileName(child->fFileName);
         }
         ref += '#' + child->fFiddle;
         if (MarkType::kClass == markType || MarkType::kStruct == markType
-                || MarkType::kMethod == markType || MarkType::kEnum == markType
+                || (MarkType::kMethod == markType && !child->fClone)
+                || MarkType::kEnum == markType
                 || MarkType::kEnumClass == markType || MarkType::kConst == markType
                 || MarkType::kMember == markType || MarkType::kDefine == markType
                 || MarkType::kTypedef == markType) {
@@ -1355,6 +1355,28 @@
         if (MarkType::kEnum == markType) {
             this->setUpSubstitutes(child, names);
         }
+        if (MarkType::kMethod == markType) {
+            if (child->fClone || child->fCloned) {
+                TextParser parser(child->fFileName, child->fStart, child->fContentStart,
+                        child->fLineCount);
+                parser.skipExact("#Method");
+                parser.skipSpace();
+                string name = child->methodName();
+                const char* nameInParser = parser.strnstr(name.c_str(), parser.fEnd);
+                parser.skipTo(nameInParser);
+                const char* paren = parser.strnchr('(', parser.fEnd);
+                parser.skipTo(paren);
+                parser.skipToBalancedEndBracket('(', ')');
+                if ("()" != string(paren, parser.fChar - paren)) {
+                    string fullName =
+                            trim_inline_spaces(string(nameInParser, parser.fChar - nameInParser));
+                    SkASSERT(names->fLinkMap.end() == names->fLinkMap.find(fullName));
+                    names->fLinkMap[fullName] = ref;
+                    SkASSERT(names->fRefMap.end() == names->fRefMap.find(fullName));
+                    names->fRefMap[fullName] = child;
+                }
+            }
+        }
         if (MarkType::kSubtopic == markType) {
             if (&fGlobalNames != names && string::npos != child->fName.find('_')) {
                 string lowered = this->loweredTopic(child->fName, child);
diff --git a/tools/bookmaker/bookmaker.cpp b/tools/bookmaker/bookmaker.cpp
index bbbcb1b..ab9b943 100644
--- a/tools/bookmaker/bookmaker.cpp
+++ b/tools/bookmaker/bookmaker.cpp
@@ -373,3 +373,38 @@
         }
     }
 }
+
+void NameMap::setParams(Definition* bmhDef, Definition* iMethod) {
+    Definition* pParent = bmhDef->csParent();
+    string parentName;
+    if (pParent) {
+        parentName = pParent->fName + "::";
+        fParent = &pParent->asRoot()->fNames;
+    }
+    fName = parentName + iMethod->fName;
+    TextParser methParams(iMethod);
+    for (auto& param : iMethod->fTokens) {
+        if (MarkType::kComment != param.fMarkType) {
+            continue;
+        }
+        TextParser paramParser(&param);
+        if (!paramParser.skipExact("@param ")) { // write parameters, if any
+            continue;
+        }
+        paramParser.skipSpace();
+        const char* start = paramParser.fChar;
+        paramParser.skipToSpace();
+        string paramName(start, paramParser.fChar - start);
+    #ifdef SK_DEBUG
+        for (char c : paramName) {
+            SkASSERT(isalnum(c) || '_' == c);
+        }
+    #endif
+        if (!methParams.containsWord(paramName.c_str(), methParams.fEnd, nullptr)) {
+            param.reportError<void>("mismatched param name");
+        }
+        fRefMap[paramName] = &param;
+        fLinkMap[paramName] = '#' + bmhDef->fFiddle + '_' + paramName;
+    }
+}
+
diff --git a/tools/bookmaker/bookmaker.h b/tools/bookmaker/bookmaker.h
index 59083a7..4fb65ca 100644
--- a/tools/bookmaker/bookmaker.h
+++ b/tools/bookmaker/bookmaker.h
@@ -215,6 +215,7 @@
 
 struct NameMap {
     void copyToParent(NameMap* parent) const;
+    void setParams(Definition* bmhDef, Definition* iMethod);
 
     string fName;
     NameMap* fParent = nullptr;
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp
index 5662bcf..523a3e8 100644
--- a/tools/bookmaker/includeParser.cpp
+++ b/tools/bookmaker/includeParser.cpp
@@ -1854,6 +1854,10 @@
 }
 
 Definition* IncludeParser::findMethod(const Definition& bmhDef) {
+    if (std::any_of(bmhDef.fChildren.begin(), bmhDef.fChildren.end(), [](Definition* def) {
+            return MarkType::kDeprecated == def->fMarkType; } )) {
+        return nullptr;
+    }
     auto doubleColon = bmhDef.fName.rfind("::");
     if (string::npos == doubleColon) {
         const auto& iGlobalMethod = fIFunctionMap.find(bmhDef.fName);
@@ -1862,7 +1866,9 @@
     }
     string className = bmhDef.fName.substr(0, doubleColon);
     const auto& iClass = fIClassMap.find(className);
-    SkASSERT(fIClassMap.end() != iClass);
+    if (fIClassMap.end() == iClass) {
+        return nullptr;
+    }
     string methodName = bmhDef.fName.substr(doubleColon + 2);
     auto& iTokens = iClass->second.fTokens;
     const auto& iMethod = std::find_if(iTokens.begin(), iTokens.end(),
diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp
index 17336bf..77851f6 100644
--- a/tools/bookmaker/includeWriter.cpp
+++ b/tools/bookmaker/includeWriter.cpp
@@ -891,21 +891,35 @@
         this->indentIn(IndentKind::kMethodOut);
         fIndentNext = false;
     }
+    if (string::npos != method->fName.find("validate")) {
+        SkDebugf("");
+    }
     if (method->fChildren.end() != std::find_if(method->fChildren.begin(), method->fChildren.end(),
             [](const Definition* def) { return MarkType::kPopulate == def->fMarkType; } )) {
-        int commentIndex = child.fParentIndex;
-        auto iter = child.fParent->fTokens.begin();
-        std::advance(iter, commentIndex);
-        SkDEBUGCODE(bool sawMethod = MarkType::kMethod == iter->fMarkType);
-        while (--commentIndex >= 0) {
-            std::advance(iter, -1);
-            if (Bracket::kSlashStar == iter->fBracket) {
-                SkASSERT(sawMethod);
+        std::list<Definition>::iterator iter;
+        const Definition* childPtr = &child;
+        SkDEBUGCODE(bool sawMethod = false);
+        do {
+            int commentIndex = childPtr->fParentIndex;
+            iter = childPtr->fParent->fTokens.begin();
+            std::advance(iter, commentIndex);
+            SkDEBUGCODE(sawMethod |= MarkType::kMethod == iter->fMarkType);
+            while (--commentIndex >= 0) {
+                std::advance(iter, -1);
+                if (Bracket::kSlashStar == iter->fBracket) {
+                    SkASSERT(sawMethod);
+                    break;
+                }
+                SkASSERT(!sawMethod);
+                SkDEBUGCODE(sawMethod |= MarkType::kMethod == iter->fMarkType);
+            }
+            if (MarkType::kMethod != iter->fMarkType) {
                 break;
             }
-            SkASSERT(!sawMethod);
-            SkDEBUGCODE(sawMethod = MarkType::kMethod == iter->fMarkType);
-        }
+            childPtr = childPtr->fParent;
+            SkDEBUGCODE(sawMethod = true);
+        } while (true);
+        SkASSERT(Bracket::kSlashSlash == iter->fBracket || Bracket::kSlashStar == iter->fBracket);
         this->lf(2);
         this->writeString("/");
         this->writeBlock(iter->length(), iter->fContentStart);
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp
index 49f8b86..b214b11 100644
--- a/tools/bookmaker/mdOut.cpp
+++ b/tools/bookmaker/mdOut.cpp
@@ -169,10 +169,6 @@
 #undef kConstTDBase
 #undef kTH_Center
 
-static void add_ref(string leadingSpaces, string ref, string* result) {
-    *result += leadingSpaces + ref;
-}
-
 static string preformat(string orig) {
     string result;
     for (auto c : orig) {
@@ -187,15 +183,6 @@
     return result;
 }
 
-static bool all_lower(string ref) {
-	for (auto ch : ref) {
-		if (!islower(ch)) {
-			return false;
-		}
-	}
-	return true;
-}
-
 // from https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
 void replace_all(string& str, const string& from, const string& to) {
     SkASSERT(!from.empty());
@@ -276,19 +263,6 @@
     return nullptr;
 }
 
-static bool formula_or_code(Resolvable resolvable) {
-    return  Resolvable::kFormula == resolvable
-            || Resolvable::kCode == resolvable;
-}
-
-static void fixup_const_function_name(string* ref) {
-    const char spaceConst[] = " const";
-    size_t spacePos = ref->rfind(spaceConst);
-    if (string::npos != spacePos && spacePos == ref->length() - sizeof(spaceConst) + 1) {
-        *ref = ref->substr(0, spacePos) + "_const";
-    }
-}
-
 struct BraceState {
     BraceState(RootDefinition* root, string name, const char* ch, KeyWord last, KeyWord keyWord,
             int count)
@@ -308,8 +282,11 @@
     int fBraceCount;
 };
 
-bool MdOut::hasWordSpace(string wordSpace) const {
-    for (NameMap* names = fNames; names; names = names->fParent) {
+bool MdOut::DefinedState::hasWordSpace(string wordSpace) const {
+    if (!fNames->fRefMap.size()) {
+        return false;
+    }
+    for (const NameMap* names = fNames; names; names = names->fParent) {
         if (names->fRefMap.end() != names->fRefMap.find(wordSpace)) {
             return true;
         }
@@ -317,558 +294,322 @@
     return false;
 }
 
-bool MdOut::phraseContinues(string phrase, string* priorWord, string* priorLink) const {
-    for (NameMap* names = fNames; names; names = names->fParent) {
+bool MdOut::DefinedState::phraseContinues(string phrase, string* priorWord,
+        string* priorLink) const {
+    for (const NameMap* names = fNames; names; names = names->fParent) {
         if (names->fRefMap.end() != names->fRefMap.find(phrase + ' ')) {
             *priorWord = phrase;
             return true;
         }
         if (names->fRefMap.end() != names->fRefMap.find(phrase)) {
             *priorWord = phrase;
-            *priorLink = names->fLinkMap.end() == names->fLinkMap.find(phrase) ? "" :
-                    names->fLinkMap[phrase];
+            auto linkIter = names->fLinkMap.find(phrase);
+            *priorLink = names->fLinkMap.end() == linkIter ? "" : linkIter->second;
             return true;
         }
     }
     return false;
 }
 
-// adds spaces to see if found word is part of registered phrase
-// adds parens to see if found word is all-lowercase function (parens must be in source as well)
-string MdOut::addIncludeReferences(const char* refStart, const char* refEnd) {
-    TextParser matrixParser(fMethod->fFileName, refStart, refEnd, fMethod->fLineCount);
-    const char* bracket = matrixParser.anyOf("|=\n");
-    bool inMatrix = bracket && ('|' == bracket[0] || '=' == bracket[0]);
-    auto& globals = fBmhParser.fGlobalNames;
-    string result;
-    const char* start = refStart;
-    string priorWord;
-    string priorLink;
-    string priorSeparator;
-    do {
-        const char* separatorStart = start;
-        bool whiteSpace = start < refEnd && ' ' >= start[0];
-        while (start < refEnd && !isalpha(start[0])) {
-            whiteSpace &= ' ' >= start[0];
-            ++start;
-        }
-        bool priorSpace = false;
-        string separator(separatorStart, start - separatorStart);
-        if ("" != priorWord && whiteSpace) {
-            string wordSpace = priorWord + ' ';
-            if (this->hasWordSpace(wordSpace)) {
-                priorWord = wordSpace;
-                priorSpace = true;
-            }
-        }
-        if ("()" == separator.substr(0, 2)) {
-            string funcRef = priorWord + "()";
-            if (this->findLink(funcRef, &priorLink)) {
-                priorWord = funcRef;
-                separator = separator.substr(2);
-            }
-        }
-        result += priorSeparator;
-        priorSeparator = separator;
-        const char* end = start;
-        do {
-            while (end < refEnd && (isalnum(end[0]) || '-' == end[0] || '_' == end[0])) {
-                ++end;
-            }
-            if (end + 1 >= refEnd || '/' != end[0] || start == end || !isalpha(end[-1])
-                    || !isalpha(end[1])) {
-                break;  // stop unless pattern is xxx/xxx as in I/O
-            }
-            ++end; // skip slash
-        } while (true);
-        while (start != end && '-' == end[-1]) {
-            --end;
-        }
-        if (start != end && end + 2 < refEnd && "()" == string(end, 2)) {
-            string check = string(start, end - start);
-            if (std::all_of(check.begin(), check.end(), [](char c) { return islower(c); })) {
-                end += 2;
-            }
-        }
-        if (start == end) {
-            break;
-        }
-        string word(start, end - start);
-        if (priorSpace) {
-            string phrase = priorWord + word;
-            if (this->phraseContinues(phrase, &priorWord, &priorLink)) {
-                start = end;
-                continue;
-            }
-            priorWord = priorWord.substr(0, priorWord.length() - 1);
-        }
-        string link;
-        bool found;
-        // TODO: operators have complicated parsing possibilities; handle the easiest for now
-        if ("operator" == priorWord && '(' == separator.back()) {
-            TextParser parser(fMethod->fFileName, separatorStart, refEnd, fMethod->fLineCount);
-            parser.skipToEndBracket('(');
-            const char* parenStart = parser.fChar;
-            parser.skipToBalancedEndBracket('(', ')');
-            word = priorWord + separator + string(parenStart + 1, parser.fChar - parenStart - 1);
-            end = parser.fChar;
-            priorWord = "";
-            priorLink = "";
-            priorSeparator = "";
-        }
-        {
-            auto paramIter = fNames->fRefMap.find(word);
-            if ((found = fNames->fRefMap.end() != paramIter) && paramIter->second) {
-                SkAssertResult(fNames->fLinkMap.end() != fNames->fLinkMap.find(word));
-                link = fNames->fLinkMap[word];
-            }
-        }
-        if (!found && ("." == separator || "->" == separator || "()." == separator
-                    || "()->" == separator)) {
-            if (('f' == word[0] && isupper(word[1])) || "()" == word.substr(word.length() - 2)
-                    || (end + 2 <= refEnd && "()" == string(end, 2))) {
-                if (fNames->fRefMap.end() != fNames->fRefMap.find(priorWord)) {
-            // find prior word's type in fMethod
-                    TextParser parser(fMethod);
-                    SkAssertResult(parser.containsWord(priorWord.c_str(), parser.fEnd,
-                            &parser.fChar));
-            // look up class or struct; trival lookup only class/struct [& * const]
-                    while (parser.back(" ") || parser.back("&") || parser.back("*")
-                            || parser.back("const"))
-                        ;
-                    const char* structEnd = parser.fChar;
-                    parser.backupWord();
-                    if (structEnd != parser.fChar) {
-                        string structName(parser.fChar, structEnd - parser.fChar);
-                        if ("SkVector" == structName) {
-                            // TODO: populate global refmap with typedefs as well as structs
-                            structName = "SkPoint";
-                        }
-                        structName += "::" + word;
-            // look for word as member of class or struct
-                        auto defIter = globals.fRefMap.find(structName);
-                        if (globals.fRefMap.end() == defIter) {
-                            structName += "()";
-                            defIter = globals.fRefMap.find(structName);
-                        }
-                        if (globals.fRefMap.end() != defIter) {
-                            found = true;
-                            SkASSERT(globals.fLinkMap.end() != globals.fLinkMap.find(structName));
-                            link = globals.fLinkMap[structName];
-                        } else {
-                            SkDebugf("probably missing struct or class member in bmh: ");
-                            SkDebugf("%s\n", structName.c_str());
-                        }
-                    }
-                }
-                if (!found) {
-                    auto& parentRefMap = fNames->fParent->fRefMap;
-                    auto priorIter = parentRefMap.find(priorWord);
-                    if (parentRefMap.end() == priorIter) {
-                        priorIter = parentRefMap.find(priorWord + "()");
-                    }
-                    if (parentRefMap.end() != priorIter) {
-                        Definition* priorDef = priorIter->second;
-                        TextParser parser(priorDef->fFileName, priorDef->fStart,
-                                priorDef->fContentStart, priorDef->fLineCount);
-                        parser.skipExact("#Method ");
-                        parser.skipSpace();
-                        parser.skipExact("const ");  // optional
-                        parser.skipSpace();
-                        const char* start = parser.fChar;
-                        parser.skipToNonAlphaNum();
-                        string structName(start, parser.fChar - start);
-                        structName += "::" + word;
-                        auto defIter = globals.fRefMap.find(structName);
-                        if (globals.fRefMap.end() != defIter) {
-                            found = true;
-                            SkASSERT(globals.fLinkMap.end() != globals.fLinkMap.find(structName));
-                            link = globals.fLinkMap[structName];
-                        }
-                    }
-                }
-            } else {
-                SkDebugf("probably missing () after function:");
-                const char* debugStart = end - 20 < refStart ? refStart : end - 20;
-                const char* debugEnd = end + 10 > refEnd ? refEnd : end + 10;
-                SkDebugf("%.*s\n", debugEnd - debugStart, debugStart);
-                SkDebugf("");
-            }
-        }
-        if (!found && "::" == separator) {
-            string fullRef = priorWord + "::" + word;
-            found = this->findLink(fullRef, &link);
-        }
-        if (!found) {
-            found = this->findLink(word, &link);
-        }
-        if (!found) {
-            found = globals.fRefMap.end() != globals.fRefMap.find(word + ' ');
-        }
-        if (!found) {
-            found = globals.fRefMap.end() != globals.fRefMap.find(word + "()");
-        }
-        if (!found) {
-            if (!inMatrix) {
-                SkDebugf("word %s not found\n", word.c_str());
-                fBmhParser.fGlobalNames.fRefMap[word] = nullptr;
-            }
-        }
-        result += "" == priorLink ? priorWord : this->anchorRef(priorLink, priorWord);
-        priorWord = word;
-        priorLink = link;
-        start = end;
-    } while (true);
-    result += "" == priorLink ? priorWord : this->anchorRef(priorLink, priorWord);
-    result += priorSeparator;
-    return result;
-}
-
-// FIXME: preserve inter-line spaces and don't add new ones
-string MdOut::addReferences(const char* refStart, const char* refEnd,
-        Resolvable resolvable) {
-    if (Resolvable::kInclude == resolvable) {  // test include resolving
-        return this->addIncludeReferences(refStart, refEnd);
+void MdOut::DefinedState::setLink() {
+    fPriorDef = nullptr;
+    // TODO: operators have complicated parsing possibilities; handle the easiest for now
+    if (fMethod && "operator" == fPriorWord && '(' == fSeparator.back()) {
+        TextParser parser(fMethod->fFileName, fSeparatorStart, fRefEnd, fMethod->fLineCount);
+        parser.skipToEndBracket('(');
+        const char* parenStart = parser.fChar;
+        parser.skipToBalancedEndBracket('(', ')');
+        parser.skipExact("_const");
+        // consume whether we find it or not
+        fWord = fPriorWord + fSeparator + string(parenStart + 1, parser.fChar - parenStart - 1);
+        fEnd = parser.fChar;
+        this->backup();
     }
-    string result;
-    MethodParser t(fRoot ? fRoot->fName : string(), fFileName, refStart, refEnd, fLineCount);
-    bool lineStart = true;
-//    bool structClassSet = false;
-    string ref;
-    string leadingSpaces;
-    int distFromParam = 99;
-    const char* lastLine = nullptr;
-    vector<BraceState> braceStack;
-    KeyWord lastKeyWord = KeyWord::kNone;
-    KeyWord keyWord = KeyWord::kNone;
-    if (Resolvable::kCode == resolvable) {
-        braceStack.push_back({fRoot, fRoot->fName, t.fChar, lastKeyWord, keyWord, 0});
+    // TODO: constructors also have complicated parsing possibilities; handle the easiest
+    else if (fMethod && fRoot && fRoot->isStructOrClass() && fRoot->fName == fPriorWord
+            && '(' == fSeparator.back()) {
+        TextParser parser(fMethod->fFileName, fSeparatorStart, fRefEnd, fMethod->fLineCount);
+        parser.skipToEndBracket('(');
+        const char* parenStart = parser.fChar;
+        parser.skipToBalancedEndBracket('(', ')');
+        string testWord = fPriorWord + fSeparator
+                + string(parenStart + 1, parser.fChar - parenStart - 1);
+        string testLink;
+        if (this->findLink(testWord, &testLink, false)) {
+            // consume only if we find it
+            fWord = testWord;
+            fLink = testLink;
+            fEnd = parser.fChar;
+            this->backup();
+            return;
+        }
     }
-    do {
-        ++distFromParam;
-        const char* base = t.fChar;
-        t.skipWhiteSpace();
-        const char* wordStart = t.fChar;
-        if (formula_or_code(resolvable) && !t.eof() && '"' == t.peek()) {
-            t.next();
-            t.skipToEndBracket('"');
-            t.next();
-            continue;
+    // look to see if text following ref is method qualifier
+    else if ((Resolvable::kYes == fResolvable || Resolvable::kClone == fResolvable)
+            && "(" == fSeparator && "" != fPriorLink) {
+        TextParser parser(fLastDef->fFileName, fSeparatorStart, fRefEnd, fLastDef->fLineCount);
+        parser.skipToBalancedEndBracket('(', ')');
+        string fullMethod = fPriorWord + string(parser.fStart, parser.fChar - parser.fStart);
+        string trimmed = trim_inline_spaces(fullMethod);
+        string testLink;
+        if (findLink(trimmed, &testLink, false)) {
+            fMethodName = fullMethod;
+            fWord = trimmed;
+            fLink = testLink;
+            fEnd = parser.fChar;
+            this->backup();
+            return;
         }
-        if (Resolvable::kCode == resolvable) {
-            int priorBrace = braceStack.back().fBraceCount;
-            int braceCount = priorBrace + t.skipToMethodStart();
-            if (braceCount > priorBrace) {
-                string name;
-                if (KeyWord::kClass == keyWord || KeyWord::kStruct == keyWord) {
-                    name = ref;
-                } else if (KeyWord::kClass == lastKeyWord && KeyWord::kPublic == keyWord) {
-                    SkASSERT(lastLine);
-                    TextParser parser(t.fFileName, lastLine, t.fChar, t.fLineCount);
-                    parser.skipSpace();
-                    SkAssertResult(parser.skipExact("class "));
-                    parser.skipSpace();
-                    const char* nameStart = parser.fChar;
-                    parser.skipToSpace();
-                    name = string(nameStart, parser.fChar - nameStart);
-                }
-                if ("" != name) {
-                    const Definition* classDef = this->isDefined(t, name, resolvable);
-                    if (!classDef) {
-                        string className = fRoot->csParent()->fName;
-                        string withRoot = className + "::" + name;
-                        classDef = this->isDefined(t, withRoot, resolvable);
-                        SkASSERT(classDef);
+    }
+    if ("." == fSeparator || "->" == fSeparator || "()." == fSeparator || "()->" == fSeparator) {
+        bool foundField = fWord.length() >= 2 && (('f' == fWord[0] && isupper(fWord[1]))
+                || "()" == fWord.substr(fWord.length() - 2)
+                || (fEnd + 2 <= fRefEnd && "()" == string(fEnd, 2)));
+        if (foundField) {
+            if (fMethod && fNames->fRefMap.end() != fNames->fRefMap.find(fPriorWord)) {
+        // find prior fWord's type in fMethod
+                TextParser parser(fMethod);
+                SkAssertResult(parser.containsWord(fPriorWord.c_str(), parser.fEnd,
+                        &parser.fChar));
+        // look up class or struct; trival lookup only class/struct [& * const]
+                while (parser.back(" ") || parser.back("&") || parser.back("*")
+                        || parser.back("const"))
+                    ;
+                const char* structEnd = parser.fChar;
+                parser.backupWord();
+                if (structEnd != parser.fChar) {
+                    string structName(parser.fChar, structEnd - parser.fChar);
+                    if ("SkVector" == structName) {
+                        // TODO: populate global refmap with typedefs as well as structs
+                        structName = "SkPoint";
+                    } else if ("SkIVector" == structName) {
+                        structName = "SkIPoint";
                     }
-                    if (classDef->isRoot()) {
-                        fRoot = const_cast<Definition*>(classDef)->asRoot();
-                        t.setLocalName(name);
+                    structName += "::" + fWord;
+        // look for fWord as member of class or struct
+                    auto defIter = fGlobals->fRefMap.find(structName);
+                    if (fGlobals->fRefMap.end() == defIter) {
+                        structName += "()";
+                        defIter = fGlobals->fRefMap.find(structName);
                     }
-                }
-                braceStack.push_back({fRoot, name, t.fChar, lastKeyWord, keyWord, braceCount});
-            } else if (braceCount < priorBrace) {
-                lastKeyWord = braceStack.back().fLastKey;
-                keyWord = braceStack.back().fKeyWord;
-                braceStack.pop_back();
-                if (KeyWord::kClass == keyWord || KeyWord::kStruct == keyWord) {
-                    fRoot = braceStack.back().fRoot;
-                    t.setLocalName(braceStack.back().fName);
-                }
-            }
-        } else {
-            (void) t.skipToMethodStart();
-        }
-        const char* start = t.fChar;
-        if (fParamEnd <= start) {
-            fParamEnd = nullptr;
-        }
-        if (wordStart < start) {
-            if (lineStart) {
-                lineStart = false;
-            } else {
-                wordStart = base;
-            }
-            string nonWord = string(wordStart, start - wordStart);
-            if (Resolvable::kFormula == resolvable) {
-                string unbreakable;
-                bool comma = false;
-                for (char c : nonWord) {
-                    if (string::npos != string("\\`*_{}[]()#+-.!").find(c)) {
-                        unbreakable += '\\';
-                    }
-                    if (' ' == c && !comma) {
-                        unbreakable += "&nbsp;";
+                    if (fGlobals->fRefMap.end() != defIter) {
+                        // example: dstInfo.width()
+                        auto structIter = fGlobals->fLinkMap.find(structName);
+                        SkASSERT(fGlobals->fLinkMap.end() != structIter);
+                        fLink = structIter->second;
+                        fPriorDef = defIter->second;
+                        return;
                     } else {
-                        unbreakable += c;
+                        SkDebugf("probably missing struct or class member in bmh: ");
+                        SkDebugf("%s\n", structName.c_str());
                     }
-                    comma = ',' == c;
-                }
-                nonWord = unbreakable;
-            }
-            result += nonWord;
-            if ('\n' != result.back()) {
-                while (start > wordStart && '\n' == start[-1]) {
-                    result += '\n';
-                    --start;
                 }
             }
-        }
-        if (lineStart) {
-            lineStart = false;
+            auto& parentRefMap = fNames->fParent->fRefMap;
+            auto priorIter = parentRefMap.find(fPriorWord);
+            if (parentRefMap.end() == priorIter) {
+                priorIter = parentRefMap.find(fPriorWord + "()");
+            }
+            if (parentRefMap.end() != priorIter) {
+                Definition* priorDef = priorIter->second;
+                if (priorDef) {
+                    TextParser parser(priorDef->fFileName, priorDef->fStart,
+                            priorDef->fContentStart, priorDef->fLineCount);
+                    parser.skipExact("#Method ");
+                    parser.skipSpace();
+                    parser.skipExact("const ");  // optional
+                    parser.skipSpace();
+                    const char* start = parser.fChar;
+                    parser.skipToNonAlphaNum();
+                    string structName(start, parser.fChar - start);
+                    structName += "::" + fWord;
+                    auto defIter = fGlobals->fRefMap.find(structName);
+                    if (fGlobals->fRefMap.end() != defIter) {
+                        // example: imageInfo().width()
+                        auto globalIter = fGlobals->fLinkMap.find(structName);
+                        SkASSERT(fGlobals->fLinkMap.end() != globalIter);
+                        fLink = globalIter->second;
+                        fPriorDef = defIter->second;
+                        return;
+                    }
+                }
+            }
         } else {
-            leadingSpaces = string(base, wordStart - base);
+            string fullRef = fPriorWord + fSeparator + fWord;
+            if (this->findLink(fullRef, &fLink, false)) {
+                return;
+            }
+            if (Resolvable::kCode != fResolvable) {
+                SkDebugf("probably missing () after function:");
+                const char* debugStart = fEnd - 20 < fRefStart ? fRefStart : fEnd - 20;
+                const char* debugEnd = fEnd + 10 > fRefEnd ? fRefEnd : fEnd + 10;
+                SkDebugf("%.*s\n", debugEnd - debugStart, debugStart);
+                SkDebugf(""); // convenient place to set a breakpoint
+            }
         }
-        t.skipToMethodEnd(resolvable);
-        if (base == t.fChar) {
-            if (!t.eof() && '~' == base[0] && !isalnum(base[1])) {
-                t.next();
-            } else {
+    }
+    // example: SkCanvas::restoreToCount
+    if ("::" == fSeparator) {
+        string fullRef = fPriorWord + "::" + fWord;
+        if (this->findLink(fullRef, &fLink, fAddParens)) {
+            return;
+        }
+    }
+    // look in parent fNames and above for match
+    if (fNames) {
+        if (this->findLink(fWord, &fLink, Resolvable::kClone == fResolvable && fAddParens)) {
+            return;
+        }
+    }
+    // example : sqrt as in "sqrt(x * x + y * y)"
+    // example : erase in seeAlso
+    if (Resolvable::kClone == fResolvable || (fEnd + 1 < fRefEnd && '(' == fEnd[0])) {
+        if (fAddParens && this->findLink(fWord + "()", &fLink, false)) {
+            return;
+        }
+    }
+    // example: Color_Type
+    if (this->findLink(fWord, &fLink, fBmhParser->fAliasMap)) {
+        return;
+    }
+    if (Resolvable::kInclude != fResolvable && string::npos != fWord.find('_')) {
+        // example: Blend_Mode
+        if (this->findLink(fWord, &fLink, fBmhParser->fTopicMap)) {
+            return;
+        }
+        if (fSubtopic) {
+            // example: Fake_Bold
+            if (fSubtopic->fName == fWord) {
+                fLink = '#' + fSubtopic->fFiddle;
+                fPriorDef = fSubtopic;
+                return;
+            }
+            const Definition* rootTopic = fSubtopic->subtopicParent();
+            if (rootTopic) {
+                if (rootTopic->fFiddle == fWord) {
+                    fLink = '#' + rootTopic->fFiddle;
+                    fPriorDef = rootTopic;
+                    return;
+                }
+                string globName = rootTopic->fFiddle + '_' + fWord;
+                if (this->findLink(globName, &fLink, fBmhParser->fTopicMap)) {
+                    return;
+                }
+            }
+        }
+        if (fRoot) {
+            string test = fRoot->fName + "::" + fWord;
+            auto rootIter = fRoot->fLeaves.find(test);
+            // example: restoreToCount in subtopic State_Stack
+            if (fRoot->fLeaves.end() != rootIter) {
+                fLink = '#' + rootIter->second.fFiddle;
+                fPriorDef = &rootIter->second;
+                return;
+            }
+        }
+    }
+    if (isupper(fWord[0]) && string::npos != fWord.find('_')) {
+        const Definition* topical = fSubtopic;
+        do {
+            string subtopic = topical->fName + '_' + fWord;
+            // example: Stroke_Width
+            if (this->findLink(subtopic, &fLink, fBmhParser->fTopicMap)) {
+                return;
+            }
+        } while ((topical = topical->topicParent()));
+    }
+    // treat hex constants as known words
+    if (fSeparator.size() > 0 && '0' == fSeparator.back() && 'x' == fWord[0]) {
+        bool allHex = true;
+        for (size_t index = 1; index < fWord.size(); ++index) {
+            char c = fWord[index];
+            if (('0' > c || '9' < c) && ('A' > c || 'F' < c)) {
+                allHex = false;
                 break;
             }
         }
-        if (start >= t.fChar) {
-            continue;
+        if (allHex) {
+            return;
         }
-        if (!t.eof() && '"' == t.peek() && start > wordStart && '"' == start[-1]) {
-            continue;
+    }
+    // treat floating constants as known words
+    if ("e" == fWord) {
+        if (std::all_of(fSeparator.begin(), fSeparator.end(), [](char c) {
+            return isdigit(c) || '.' == c || '-' == c || ' ' >= c;
+        })) {
+            return;
         }
-        ref = string(start, t.fChar - start);
-        if (fParamEnd
-                && islower(start[0]) && ('k' != start[0] || !isupper(start[1]))) {
-            if (' ' == start[-1] && ' ' != result.back()) {
-                result += ' ';
+    }
+    // stop short of parsing example; just look to see if it contains fWord in description
+    if (fLastDef && MarkType::kDescription == fLastDef->fMarkType) {
+        Definition* example = fLastDef->fParent;
+        if (MarkType::kExample == example->fMarkType) {
+            // example text is blocked by last child before std out, if it exists
+            const char* exStart = example->fChildren.back()->fContentEnd;
+            const char* exEnd = example->fContentEnd;
+            if (MarkType::kStdOut == example->fChildren.back()->fMarkType) {
+                exStart = example->fChildren[example->fChildren.size() - 2]->fContentEnd;
+                exEnd = example->fChildren.back()->fContentStart;
             }
-            result += ref;
-            continue;
-        }
-        if (Resolvable::kCode == resolvable) {
-            fixup_const_function_name(&ref);
-        }
-        if (Definition* def = this->isDefined(t, ref, resolvable)) {
-            if (MarkType::kExternal == def->fMarkType) {
-                (void) this->anchorRef("undocumented#" + ref, "");   // for anchor validate
-                add_ref(leadingSpaces, ref, &result);
-                continue;
-            }
-            SkASSERT(def->fFiddle.length());
-            if (Resolvable::kSimple != resolvable
-                    && Resolvable::kInclude != resolvable
-                    && !t.eof() && '(' == t.peek() && t.strnchr(')', t.fEnd)) {
-                TextParserSave tSave(&t);
-                if (!t.skipToBalancedEndBracket('(', ')')) {
-                    tSave.restore();
-                    t.reportError("missing close paren");
-                    fAddRefFailed = true;
-                    return result;
-                }
-//                t.next();
-                SkASSERT(')' == t.fChar[-1]);
-                fParamEnd = t.fChar - 1;    // don't resolve lower case words til param end
-                string fullRef = string(start, t.fChar - start);
-                // if _2 etc alternates are defined, look for paren match
-                // may ignore () if ref is all lower case and resolvable is not code
-                // otherwise flag as error
-                int suffix = '2';
-                bool foundMatch = false;
-                Definition* altDef = def;
-                while (altDef && suffix <= '9') {
-                    if ((foundMatch = altDef->paramsMatch(fullRef, ref))) {
-                        def = altDef;
-                        ref = fullRef;
-                        break;
-                    }
-                    string altTest = ref + '_';
-                    altTest += suffix++;
-                    altDef = this->isDefined(t, altTest, Resolvable::kOut);
-                }
-                if (suffix > '9') {
-                    t.reportError("too many alts");
-                    fAddRefFailed = true;
-                    return result;
-                }
-                if (!foundMatch) {
-                    if (!(def = this->isDefined(t, fullRef, resolvable))) {
-                        if (formula_or_code(resolvable)) {
-                            // TODO: look for looser mapping -- if methods name match, look for
-                            //   unique mapping based on number of parameters
-                            // for now, just look for function name match
-                            def = this->isDefined(t, ref, resolvable);
-                        }
-                        if (!def && !result.size()) {
-                            t.reportError("missing method");
-                            fAddRefFailed = true;
-                            return result;
-                        }
-                    }
-                    ref = fullRef;
-                }
-                ref = ref.substr(0, ref.find('('));
-                tSave.restore();
-			} else if (Resolvable::kClone != resolvable &&
-					all_lower(ref) && (t.eof() || '(' != t.peek())) {
-				add_ref(leadingSpaces, ref, &result);
-				continue;
-			}
-            if (!def) {
-                t.reportError("missing method");
-                fAddRefFailed = true;
-                return result;
-            }
-			result += linkRef(leadingSpaces, def, ref, resolvable);
-            if (!t.eof() && '(' == t.peek()) {
-                if (Resolvable::kInclude == resolvable
-                       && std::any_of(ref.begin(), ref.end(), [](char c){ return !islower(c); } )) {
-                    t.next();  // skip open paren
-                    SkAssertResult(')' == t.next());  // skip close paren
-                    continue;
-                }
-                result += t.next();  // skip open paren
-            }
-            continue;
-        }
-        if (!t.eof() && '(' == t.peek()) {
-            if (!t.skipToEndBracket(')')) {
-                t.reportError("missing close paren");
-                fAddRefFailed = true;
-                return result;
-            }
-            t.next();
-            ref = string(start, t.fChar - start);
-            if (Definition* def = this->isDefined(t, ref, Resolvable::kYes)) {
-                SkASSERT(def->fFiddle.length());
-				result += linkRef(leadingSpaces, def, ref, resolvable);
-                continue;
+            // maybe need a general function that searches block text excluding children
+            TextParser exParse(example->fFileName, exStart, exEnd, example->fLineCount);
+            if (exParse.containsWord(fWord.c_str(), exParse.fEnd, nullptr)) {
+                return;
             }
         }
-// class, struct, and enum start with capitals
-// methods may start with upper (static) or lower (most)
+    }
+    // example: (x1, y1) after arcTo(SkScalar x1, ...
+    if (Resolvable::kYes == fResolvable && "" != fSeparator
+            && ('(' == fSeparator.back() || ',' == fSeparator[0])
+            && string::npos != fMethodName.find(fWord)) {
+        return;
+    }
+    // example: <sup>  (skip html)
+    if (Resolvable::kYes == fResolvable && fEnd + 1 < fRefEnd && '>' == fEnd[0] && "" != fSeparator
+            && ('<' == fSeparator.back() || (fSeparator.size() >= 2
+            && "</" == fSeparator.substr(fSeparator.size() - 2)))) {
+        return;
+    }
+    // TODO: can probably resolve formulae, but need a way for formula to define new reference
+    // for example: Given: #Formula # Sa ## as source Alpha,
+    // for example: where #Formula # m = Da > 0 ? Dc / Da : 0 ##;
+    if (!fInProgress && Resolvable::kSimple != fResolvable
+            && Resolvable::kCode != fResolvable && Resolvable::kFormula != fResolvable) {
+        // example: Coons as in "Coons patch"
+        bool withSpace = fEnd + 1 < fRefEnd && ' ' == fEnd[0]
+                && fGlobals->fRefMap.end() != fGlobals->fRefMap.find(fWord + ' ');
+        if (!withSpace && (Resolvable::kInclude == fResolvable ? !fInMatrix :
+                '"' != fPriorSeparator.back() || '"' != fSeparator.back())) {
+            SkDebugf("fWord %s not found\n", fWord.c_str());
+            fBmhParser->fGlobalNames.fRefMap[fWord] = nullptr;
+        }
+    }
+}
 
-        // see if this should have been a findable reference
 
-            // look for Sk / sk / SK ..
-        if (!ref.compare(0, 2, "Sk") && ref != "Skew" && ref != "Skews" && ref != "Skewing"
-              && ref != "Skip" && ref != "Skips") {
-            if (Resolvable::kOut != resolvable && !formula_or_code(resolvable)) {
-                t.reportError("missed Sk prefixed");
-                fAddRefFailed = true;
-                return result;
-            }
+string MdOut::addReferences(const char* refStart, const char* refEnd, Resolvable resolvable) {
+    DefinedState s(*this, refStart, refEnd, resolvable);
+    string result;
+    const char* start = refStart;
+    do {
+        s.fSeparatorStart = start;
+        start = s.skipWhiteSpace();
+        s.skipParens();
+        result += s.nextSeparator(start);
+        if (s.findEnd(start)) {
+            break;
         }
-        if (!ref.compare(0, 2, "SK")) {
-            if (Resolvable::kOut != resolvable && !formula_or_code(resolvable)) {
-                t.reportError("missed SK prefixed");
-                fAddRefFailed = true;
-                return result;
-            }
-        }
-        if (!isupper(start[0])) {
-            // TODO:
-            // look for all lowercase w/o trailing parens as mistaken method matches
-            // will also need to see if Example Description matches var in example
-            Definition* def = nullptr;
-            if (fMethod && (def = fMethod->hasParam(ref))) {
-				result += linkRef(leadingSpaces, def, ref, resolvable);
-                fLastParam = def;
-                distFromParam = 0;
-                continue;
-            } else if (!fInDescription && ref[0] != '0'
-                    && string::npos != ref.find_first_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) {
-                // FIXME: see isDefined(); check to see if fXX is a member of xx.fXX
-                if (('f' != ref[0] && string::npos == ref.find("()"))
-//                        || '.' != t.backup(ref.c_str())
-                        && ('k' != ref[0] && string::npos == ref.find("_Private"))) {
-                    if ('.' == wordStart[0] && (distFromParam >= 1 && distFromParam <= 16)) {
-                        Definition* paramType = this->findParamType();
-                        if (paramType) {
-                            string fullName = paramType->fName + "::" + ref;
-                            if (paramType->hasMatch(fullName)) {
-								result += linkRef(leadingSpaces, paramType, ref, resolvable);
-                                continue;
-                            }
-                        }
-                    }
-                    if (Resolvable::kSimple != resolvable
-                            && Resolvable::kInclude != resolvable
-                            && Resolvable::kOut != resolvable
-                            && !formula_or_code(resolvable)) {
-                        t.reportError("missed camelCase");
-                        fAddRefFailed = true;
-                        return result;
-                    }
-                }
-            }
-            KeyWord newKeyWord = IncludeParser::FindKey(start, start + ref.length());
-            lastLine = nullptr;
-            if (KeyWord::kPrivate != newKeyWord && KeyWord::kProtected != newKeyWord
-                    && KeyWord::kPublic != newKeyWord) {
-                lastKeyWord = keyWord;
-                keyWord = newKeyWord;
-            } else if (Resolvable::kCode == resolvable && ':' != t.peek()) {
-                lastLine = t.fLine;
-                lastKeyWord = keyWord;
-                keyWord = newKeyWord;
-            }
-            add_ref(leadingSpaces, ref, &result);
+        s.fWord = string(start, s.fEnd - start);
+        s.setLower();
+        if (s.setPriorSpaceWord(&start)) {
             continue;
         }
-        auto topicIter = fBmhParser.fTopicMap.find(ref);
-        if (topicIter != fBmhParser.fTopicMap.end()) {
-			result += linkRef(leadingSpaces, topicIter->second, ref, resolvable);
-            continue;
-        }
-        bool startsSentence = t.sentenceEnd(start);
-        if (!t.eof() && ' ' != t.peek()) {
-			add_ref(leadingSpaces, ref, &result);
-            continue;
-        }
-        if (t.fChar + 1 >= t.fEnd || (!isupper(t.fChar[1]) && startsSentence)) {
-			add_ref(leadingSpaces, ref, &result);
-            continue;
-        }
-        if (isupper(t.fChar[1]) && startsSentence) {
-            TextParser next(t.fFileName, &t.fChar[1], t.fEnd, t.fLineCount);
-            string nextWord(next.fChar, next.wordEnd() - next.fChar);
-            if (this->isDefined(t, nextWord, Resolvable::kYes)) {
-				add_ref(leadingSpaces, ref, &result);
-                continue;
-            }
-        }
-        Definition* def = this->checkParentsForMatch(fSubtopic, ref);
-        if (!def) {
-            def = this->checkParentsForMatch(fRoot, ref);
-        }
-        if (def) {
- 			result += this->linkRef(leadingSpaces, def, ref, resolvable);
-            continue;
-        }
-        if (Resolvable::kOut != resolvable &&
-                !formula_or_code(resolvable)) {
-            t.reportError("undefined reference");
-            fAddRefFailed = true;
-        } else {
-			add_ref(leadingSpaces, ref, &result);
-        }
-    } while (!t.eof());
+        s.setLink();
+        result += "" == s.fPriorLink ? s.fPriorWord : this->anchorRef(s.fPriorLink, s.fPriorWord);
+        start = s.nextWord();
+    } while (true);
+    result += "" == s.fPriorLink ? s.fPriorWord : this->anchorRef(s.fPriorLink, s.fPriorWord);
+    result += s.fPriorSeparator;
     return result;
 }
 
@@ -918,6 +659,9 @@
     if (SkStrEndsWith(name, "illustrations.bmh")) {  // don't look inside this for now
         return true;
     }
+    if (SkStrEndsWith(name, "undocumented.bmh")) {  // don't look inside this for now
+        return true;
+    }
     fFileName = string(name);
     string filename(name);
     if (filename.substr(filename.length() - 4) == ".bmh") {
@@ -1082,7 +826,8 @@
     if (!islower(descriptionStart[0]) && !isdigit(descriptionStart[0])) {
         paramBody.skipToNonName();
         string ref = string(descriptionStart, paramBody.fChar - descriptionStart);
-        if (!this->isDefined(paramBody, ref, Resolvable::kYes)) {
+        if (!std::all_of(ref.begin(), ref.end(), [](char c) { return isupper(c); })
+                && !this->isDefined(paramBody, Resolvable::kYes)) {
             string errorStr = MarkType::kReturn == def->fMarkType ? "return" : "param";
             errorStr += " description must start with lower case";
             paramBody.reportError(errorStr.c_str());
@@ -1115,6 +860,9 @@
         }
         end = child->fStart;
         if (Resolvable::kNo != resolvable) {
+            if (def->isStructOrClass() || MarkType::kEnumClass == def->fMarkType) {
+                fNames = &def->asRoot()->fNames;
+            }
             this->resolveOut(start, end, resolvable);
         }
         this->markTypeOut(child, &prior);
@@ -1202,34 +950,60 @@
     return csParent;
 }
 
-bool MdOut::findLink(string word, string* linkPtr) {
-    NameMap* names = fNames;
-    while ((names = names->fParent)) {
+bool MdOut::DefinedState::findLink(string word, string* linkPtr, bool addParens) {
+    const NameMap* names = fNames;
+    do {
         auto localIter = names->fRefMap.find(word);
         if (names->fRefMap.end() != localIter) {
-            if (localIter->second) {
-                SkAssertResult(names->fLinkMap.end() != names->fLinkMap.find(word));
-                *linkPtr = names->fLinkMap[word];
+            if ((fPriorDef = localIter->second)) {
+                auto linkIter = names->fLinkMap.find(word);
+                SkAssertResult(names->fLinkMap.end() != linkIter);
+                *linkPtr = linkIter->second;
             }
             return true;
         }
         if (!names->fParent && isupper(word[0])) {
-            SkASSERT(names == &fBmhParser.fGlobalNames);
+            SkASSERT(names == &fBmhParser->fGlobalNames);
             string lower = (char) tolower(word[0]) + word.substr(1);
             auto globalIter = names->fRefMap.find(lower);
             if (names->fRefMap.end() != globalIter) {
-                if (globalIter->second) {
-                    SkAssertResult(names->fLinkMap.end() != names->fLinkMap.find(lower));
-                    *linkPtr = names->fLinkMap[lower];
+                if ((fPriorDef = globalIter->second)) {
+                    auto lowerIter = names->fLinkMap.find(lower);
+                    SkAssertResult(names->fLinkMap.end() != lowerIter);
+                    *linkPtr = lowerIter->second;
                 }
                 return true;
             }
         }
+        if (addParens) {
+            string parenWord = word + "()";
+            auto paramIter = names->fRefMap.find(parenWord);
+            if (names->fRefMap.end() != paramIter) {
+                if ((fPriorDef = paramIter->second)) {
+                    auto parenIter = names->fLinkMap.find(parenWord);
+                    SkAssertResult(names->fLinkMap.end() != parenIter);
+                    *linkPtr = parenIter->second;
+                }
+                return true;
+            }
+        }
+    } while ((names = names->fParent));
+    return false;
+}
+
+bool MdOut::DefinedState::findLink(string word, string* linkPtr,
+        unordered_map<string, Definition*>& map) {
+    auto mapIter = map.find(word);
+    if (map.end() != mapIter) {
+        if ((fPriorDef = mapIter->second)) {
+            *linkPtr = '#' + mapIter->second->fFiddle;
+        }
+        return true;
     }
     return false;
 }
 
-Definition* MdOut::findParamType() {
+const Definition* MdOut::findParamType() {
     SkASSERT(fMethod);
     TextParser parser(fMethod->fFileName, fMethod->fStart, fMethod->fContentStart,
             fMethod->fLineCount);
@@ -1244,8 +1018,7 @@
         SkASSERT(!parser.eof());
         string name = string(word, parser.fChar - word);
         if (fLastParam->fName == name) {
-            Definition* paramType = this->isDefined(parser, lastFull,
-                    Resolvable::kOut);
+            const Definition* paramType = this->isDefined(parser, Resolvable::kOut);
             return paramType;
         }
         if (isupper(name[0])) {
@@ -1303,185 +1076,22 @@
     FPRINTF("%s", s.c_str());
 }
 
-Definition* MdOut::isDefinedByParent(RootDefinition* root, string ref) {
-    if (ref == root->fName) {
-        return root;
-    }
-    if (Definition* definition = root->find(ref, RootDefinition::AllowParens::kYes)) {
-        return definition;
-    }
-    Definition* test = root;
-    bool isSubtopic = MarkType::kSubtopic == root->fMarkType
-        || MarkType::kTopic == root->fMarkType;
+const Definition* MdOut::isDefined(const TextParser& parser, Resolvable resolvable) {
+    DefinedState s(*this, parser.fStart, parser.fEnd, resolvable);
+    const char* start = parser.fStart;
     do {
-        if (!test->isRoot()) {
-            continue;
-        }
-        bool testIsSubtopic = MarkType::kSubtopic == test->fMarkType
-                || MarkType::kTopic == test->fMarkType;
-        if (isSubtopic != testIsSubtopic) {
-            continue;
-        }
-        RootDefinition* root = test->asRoot();
-        for (auto& leaf : root->fBranches) {
-            if (ref == leaf.first) {
-                return leaf.second;
-            }
-            Definition* definition = leaf.second->find(ref,
-                    RootDefinition::AllowParens::kYes);
-            if (definition) {
-                return definition;
-            }
-        }
-        string prefix = isSubtopic ? "_" : "::";
-        string prefixed = root->fName + prefix + ref;
-        if (Definition* definition = root->find(prefixed, RootDefinition::AllowParens::kYes)) {
-            return definition;
-        }
-        if (isSubtopic && isupper(prefixed[0])) {
-            auto topicIter = fBmhParser.fTopicMap.find(prefixed);
-            if (topicIter != fBmhParser.fTopicMap.end()) {
-                return topicIter->second;
-            }
-        }
-        if (isSubtopic) {
-            string fiddlePrefixed = root->fFiddle + "_" + ref;
-            auto topicIter = fBmhParser.fTopicMap.find(fiddlePrefixed);
-            if (topicIter != fBmhParser.fTopicMap.end()) {
-                return topicIter->second;
-            }
-        }
-    } while ((test = test->fParent));
-    return nullptr;
-}
-
-Definition* MdOut::isDefined(const TextParser& parser, string ref,
-        Resolvable resolvable) {
-    auto rootIter = fBmhParser.fClassMap.find(ref);
-    if (rootIter != fBmhParser.fClassMap.end()) {
-        return &rootIter->second;
-    }
-    auto typedefIter = fBmhParser.fTypedefMap.find(ref);
-    if (typedefIter != fBmhParser.fTypedefMap.end()) {
-        return &typedefIter->second;
-    }
-    auto enumIter = fBmhParser.fEnumMap.find(ref);
-    if (enumIter != fBmhParser.fEnumMap.end()) {
-        return &enumIter->second;
-    }
-    auto constIter = fBmhParser.fConstMap.find(ref);
-    if (constIter != fBmhParser.fConstMap.end()) {
-        return &constIter->second;
-    }
-    auto methodIter = fBmhParser.fMethodMap.find(ref);
-    if (methodIter != fBmhParser.fMethodMap.end()) {
-        return &methodIter->second;
-    }
-    auto aliasIter = fBmhParser.fAliasMap.find(ref);
-    if (aliasIter != fBmhParser.fAliasMap.end()) {
-        return aliasIter->second;
-    }
-    auto defineIter = fBmhParser.fDefineMap.find(ref);
-    if (defineIter != fBmhParser.fDefineMap.end()) {
-        return &defineIter->second;
-    }
-    for (auto& external : fBmhParser.fExternals) {
-        if (external.fName == ref) {
-            return &external;
-        }
-    }
-    if (Definition* definition = this->isDefinedByParent(fRoot, ref)) {
-        return definition;
-    }
-    if (Definition* definition = this->isDefinedByParent(fSubtopic, ref)) {
-        return definition;
-    }
-    size_t doubleColon = ref.find("::");
-    if (string::npos != doubleColon) {
-        string className = ref.substr(0, doubleColon);
-        auto classIter = fBmhParser.fClassMap.find(className);
-        if (classIter != fBmhParser.fClassMap.end()) {
-            RootDefinition& classDef = classIter->second;
-            Definition* result = classDef.find(ref, RootDefinition::AllowParens::kYes);
-            if (result) {
-                return result;
-            }
-        }
-
-    }
-    if (!ref.compare(0, 2, "SK") || !ref.compare(0, 3, "sk_")
-            || (('k' == ref[0] || 'g' == ref[0] || 'f' == ref[0]) &&
-                ref.length() > 1 && isupper(ref[1]))) {
-        // try with a prefix
-        if ('k' == ref[0]) {
-            for (auto& iter : fBmhParser.fEnumMap) {
-                auto def = iter.second.find(ref, RootDefinition::AllowParens::kYes);
-                if (def) {
-                    return def;
-                }
-            }
-            if (fEnumClass) {
-                string fullName = fEnumClass->fName + "::" + ref;
-                for (auto child : fEnumClass->fChildren) {
-                    if (fullName == child->fName) {
-                        return child;
-                    }
-                }
-            }
-            if (string::npos != ref.find("_Private")) {
-                return nullptr;
-            }
-        }
-        if ('f' == ref[0]) {
-            // FIXME : find def associated with prior, e.g.: r.fX where 'SkPoint r' was earlier
-                // need to have pushed last resolve on stack to do this
-                // for now, just try to make sure that it's there and error if not
-            if ('.' != parser.backup(ref.c_str())) {
-                parser.reportError("fX member undefined");
-                fAddRefFailed = true;
-                return nullptr;
-            }
-        } else {
-            if (Resolvable::kOut != resolvable &&
-                    !formula_or_code(resolvable)) {
-                parser.reportError("SK undefined");
-                fAddRefFailed = true;
-            }
+        s.fSeparatorStart = start;
+        start = s.skipWhiteSpace();
+        s.skipParens();
+        (void) s.nextSeparator(start);
+        if (s.findEnd(start)) {
             return nullptr;
         }
-    }
-    if (isupper(ref[0])) {
-        auto topicIter = fBmhParser.fTopicMap.find(ref);
-        if (topicIter != fBmhParser.fTopicMap.end()) {
-            return topicIter->second;
-        }
-        size_t pos = ref.find('_');
-        if (string::npos != pos) {
-            // see if it is defined by another base class
-            string className(ref, 0, pos);
-            auto classIter = fBmhParser.fClassMap.find(className);
-            if (classIter != fBmhParser.fClassMap.end()) {
-                if (Definition* definition = classIter->second.find(ref,
-                        RootDefinition::AllowParens::kYes)) {
-                    return definition;
-                }
-            }
-            auto enumIter = fBmhParser.fEnumMap.find(className);
-            if (enumIter != fBmhParser.fEnumMap.end()) {
-                if (Definition* definition = enumIter->second.find(ref,
-                        RootDefinition::AllowParens::kYes)) {
-                    return definition;
-                }
-            }
-            if (Resolvable::kOut != resolvable &&
-                    !formula_or_code(resolvable)) {
-                parser.reportError("_ undefined");
-                fAddRefFailed = true;
-            }
-            return nullptr;
-        }
-    }
-    return nullptr;
+        s.fWord = string(start, s.fEnd - start);
+        s.setLower();
+    } while (s.setPriorSpaceWord(&start));
+    s.setLink();
+    return s.fPriorDef;
 }
 
 string MdOut::linkName(const Definition* ref) const {
@@ -1499,137 +1109,6 @@
     return result;
 }
 
-// for now, hard-code to html links
-// def should not include SkXXX_
-string MdOut::linkRef(string leadingSpaces, Definition* def,
-        string ref, Resolvable resolvable) {
-    bool trimRef = Resolvable::kInclude == resolvable && "Sk" == ref.substr(0, 2);
-    if (trimRef) {
-#ifdef SK_DEBUG
-    for (auto c : ref) {
-            SkASSERT(isalpha(c) || isdigit(c));
-        }
-#endif
-        ref = ref.substr(2);
-    }
-    string buildup;
-    string refName;
-    const string* str = &def->fFiddle;
-    string classPart = *str;
-    bool fromInclude = "" == classPart;
-    if (fromInclude) {
-        const Definition* parent = def->csParent();
-        SkASSERT(parent);
-        classPart = parent->fName;
-        auto bmhMap = fBmhParser.fClassMap.find(classPart);
-        auto defName = def->fParent->fName;
-        SkASSERT(fBmhParser.fClassMap.end() != bmhMap);
-        string fullName = classPart + "::" + defName;
-        auto bmhDef = bmhMap->second.fLeaves.find(fullName);
-        if (bmhMap->second.fLeaves.end() == bmhDef) {
-            bmhDef = bmhMap->second.fLeaves.find(fullName + "()");
-        }
-        SkASSERT(bmhMap->second.fLeaves.end() != bmhDef);
-        refName = bmhDef->second.fFiddle;
-        refName += '_' + ref;
-    }
-    SkASSERT(classPart.length() > 0);
-    bool globalEnumMember = false;
-    if (MarkType::kAlias == def->fMarkType) {
-        def = def->fParent;
-        SkASSERT(def);
-        SkASSERT(MarkType::kSubtopic == def->fMarkType
-            || MarkType::kTopic == def->fMarkType
-            || MarkType::kConst == def->fMarkType);
-    }
-    if (MarkType::kSubtopic == def->fMarkType) {
-        const Definition* topic = def->topicParent();
-        SkASSERT(topic);
-        classPart = topic->fName;
-        refName = def->fName;
-    } else if (MarkType::kTopic == def->fMarkType) {
-        refName = def->fName;
-    } else {
-        if ('k' == classPart[0] && string::npos != classPart.find("_Sk")) {
-            globalEnumMember = true;
-        } else {
-            SkASSERT("Sk" == classPart.substr(0, 2) || "SK" == classPart.substr(0, 2)
-                    // FIXME: kitchen sink catch below, need to do better
-                    || string::npos != def->fFileName.find("undocumented"));
-            size_t under = classPart.find('_');
-            if (string::npos != under) {
-                classPart = classPart.substr(0, under);
-            }
-        }
-        if (!fromInclude) {
-            refName = def->fFiddle;
-            if (trimRef) {
-                SkASSERT("Sk" == refName.substr(0, 2));
-#ifdef SK_DEBUG
-                for (auto c : refName) {
-                    SkASSERT(isalpha(c) || isdigit(c));
-                }
-#endif
-                refName = refName.substr(2);
-            }
-        }
-    }
-    bool classMatch = fRoot->fFileName == def->fFileName || fromInclude;
-    SkASSERT(fRoot);
-    SkASSERT(fRoot->fFileName.length());
-    if (!classMatch) {
-        string filename = def->fFileName;
-        if (filename.substr(filename.length() - 4) == ".bmh") {
-            filename = filename.substr(0, filename.length() - 4);
-        }
-        size_t start = filename.length();
-        while (start > 0 && (isalnum(filename[start - 1]) || '_' == filename[start - 1])) {
-            --start;
-        }
-        buildup = filename.substr(start);
-    }
-    buildup += "#" + refName;
-    if (MarkType::kParam == def->fMarkType) {
-        const Definition* parent = def->fParent;
-        SkASSERT(MarkType::kMethod == parent->fMarkType);
-        buildup = '#' + parent->fFiddle + '_' + ref;
-    }
-    string refOut(ref);
-    if (!globalEnumMember && Resolvable::kCode != resolvable) {
-        std::replace(refOut.begin(), refOut.end(), '_', ' ');
-    }
-    if (ref.length() > 2 && islower(ref[0]) && "()" == ref.substr(ref.length() - 2)
-            && Resolvable::kCode != resolvable) {
-        refOut = refOut.substr(0, refOut.length() - 2);
-    }
-    string result = leadingSpaces + this->anchorRef(buildup, refOut);
-	if (Resolvable::kClone == resolvable && MarkType::kMethod == def->fMarkType &&
-			def->fCloned && !def->fClone) {
-		bool found = false;
-		string match = def->fName;
-		if ("()" == match.substr(match.length() - 2)) {
-			match = match.substr(0, match.length() - 2);
-		}
-		match += '_';
-		auto classIter = fBmhParser.fClassMap.find(classPart);
-		if (fBmhParser.fClassMap.end() != classIter) {
-			for (char num = '2'; num <= '9'; ++num) {
-				string clone = match + num;
-				const auto& leafIter = classIter->second.fLeaves.find(clone);
-				if (leafIter != classIter->second.fLeaves.end()) {
-					result += "<sup>" + this->anchorRef(buildup + "_" + num,
-                             string("[") + num + "]") + "</sup>";
-					found = true;
-				}
-			}
-		}
-		if (!found) {
-			SkDebugf("");  // convenient place to set a breakpoint
-		}
-	}
-	return result;
-}
-
 static bool writeTableEnd(MarkType markType, Definition* def, const Definition** prior) {
     return markType != def->fMarkType && *prior && markType == (*prior)->fMarkType;
 }
@@ -1773,6 +1252,7 @@
         fTableState = TableState::kNone;
     }
     fLastDef = def;
+    NameMap paramMap;
     switch (def->fMarkType) {
         case MarkType::kAlias:
             break;
@@ -2041,6 +1521,18 @@
             this->lf(2);
             fTableState = TableState::kNone;
             fMethod = def;
+            if ("SkTextBlobBuilder::allocRun_2" == def->fName) {
+                SkDebugf("");
+            }
+            Definition* iMethod = fIncludeParser.findMethod(*def);
+            if (iMethod) {
+                fMethod = iMethod;
+                paramMap.fParent = &fBmhParser.fGlobalNames;
+                paramMap.setParams(def, iMethod);
+                fNames = &paramMap;
+            } else {
+                SkDebugf("");
+            }
             } break;
         case MarkType::kNoExample:
             break;
@@ -2180,45 +1672,9 @@
                 SkASSERT(MarkType::kMethod == parent->fMarkType);
                 // retrieve parameters, return, description from include
                 Definition* iMethod = fIncludeParser.findMethod(*parent);
+                SkASSERT(iMethod);  // deprecated or 'in progress' functions should not include populate
                 bool wroteParam = false;
-                fMethod = iMethod;
-                NameMap paramMap;
-                Definition* pParent = def->csParent();
-                string parentName;
-                NameMap* parentMap;
-                if (pParent) {
-                    parentName = pParent->fName + "::";
-                    parentMap = &pParent->asRoot()->fNames;
-                } else {
-                    parentMap = &fBmhParser.fGlobalNames;
-                }
-                paramMap.fName = parentName + iMethod->fName;
-                paramMap.fParent = parentMap;
-                fNames = &paramMap;
-                TextParser methParams(iMethod);
-                for (auto& param : iMethod->fTokens) {
-                    if (MarkType::kComment != param.fMarkType) {
-                        continue;
-                    }
-                    TextParser paramParser(&param);
-                    if (!paramParser.skipExact("@param ")) { // write parameters, if any
-                        continue;
-                    }
-                    paramParser.skipSpace();
-                    const char* start = paramParser.fChar;
-                    paramParser.skipToSpace();
-                    string paramName(start, paramParser.fChar - start);
-                #ifdef SK_DEBUG
-                    for (char c : paramName) {
-                        SkASSERT(isalnum(c) || '_' == c);
-                    }
-                #endif
-                    if (!methParams.containsWord(paramName.c_str(), methParams.fEnd, nullptr)) {
-                        param.reportError<void>("mismatched param name");
-                    }
-                    paramMap.fRefMap[paramName] = &param;
-                    paramMap.fLinkMap[paramName] = '#' + def->fFiddle + '_' + paramName;
-                }
+                SkASSERT(fMethod == iMethod);
                 for (auto& entry : iMethod->fTokens) {
                     if (MarkType::kComment != entry.fMarkType) {
                         continue;
@@ -2262,8 +1718,6 @@
                             Resolvable::kInclude);  // write description
                     this->lf(1);
                 }
-                fMethod = nullptr;
-                fNames = fNames->fParent;
             }
             } break;
         case MarkType::kPrivate:
@@ -2482,6 +1936,7 @@
             } break;
         case MarkType::kMethod:
             fMethod = nullptr;
+            fNames = fNames->fParent;
             break;
         case MarkType::kConst:
         case MarkType::kMember:
@@ -2916,7 +2371,6 @@
             start = entry->fName.substr(0, start - 1).rfind("::");
         }
         string entryName = entry->fName.substr(string::npos == start ? 0 : start + 1);
-//        fixup_const_function_name(&entry->fName);
         items[entryName] = entry;
     }
     for (auto entry : items) {
@@ -2962,7 +2416,7 @@
         return parser.reportError<bool>("missing #Line");
     }
     TextParser dummy(entry); // for reporting errors, which we won't do
-    if (!this->isDefined(dummy, keyName, Resolvable::kOut)) {
+    if (!this->isDefined(dummy, Resolvable::kOut)) {
         keyName = entry->fName;
         size_t doubleColon = keyName.find("::");
         SkASSERT(string::npos != doubleColon);
diff --git a/tools/bookmaker/mdOut.h b/tools/bookmaker/mdOut.h
index 30b48d8..8a10854 100644
--- a/tools/bookmaker/mdOut.h
+++ b/tools/bookmaker/mdOut.h
@@ -45,9 +45,159 @@
         MarkType fMarkType;
     };
 
+    struct DefinedState {
+        DefinedState(const MdOut& mdOut, const char* refStart, const char* refEnd,
+                Resolvable resolvable)
+            : fBmhParser(&mdOut.fBmhParser)
+            , fNames(mdOut.fNames)
+            , fGlobals(&mdOut.fBmhParser.fGlobalNames)
+            , fLastDef(mdOut.fLastDef)
+            , fMethod(mdOut.fMethod)
+            , fSubtopic(mdOut.fSubtopic)
+            , fRoot(mdOut.fRoot)
+            , fRefStart(refStart)
+            , fRefEnd(refEnd)
+            , fResolvable(resolvable)
+            , fInProgress(mdOut.fInProgress) {
+            TextParser matrixParser(fLastDef->fFileName, refStart, refEnd, fLastDef->fLineCount);
+            const char* bracket = matrixParser.anyOf("|=\n");
+            fInMatrix = bracket && ('|' == bracket[0] || '=' == bracket[0]);
+        }
+
+        void backup() {
+            fPriorWord = fBack2Word;
+            fPriorLink = "";
+            fPriorSeparator = "";
+            fSeparator = fBack2Separator;
+        }
+
+        bool findEnd(const char* start) {
+            do {
+                while (fEnd < fRefEnd && (isalnum(fEnd[0]) || '-' == fEnd[0] || '_' == fEnd[0])) {
+                    ++fEnd;
+                }
+                if (fEnd + 1 >= fRefEnd || '/' != fEnd[0] || start == fEnd || !isalpha(fEnd[-1])
+                        || !isalpha(fEnd[1])) {
+                    break;  // stop unless pattern is xxx/xxx as in I/O
+                }
+                ++fEnd; // skip slash
+            } while (true);
+            while (start != fEnd && '-' == fEnd[-1]) {
+                --fEnd;
+            }
+            return start == fEnd;
+        }
+
+        bool findLink(string ref, string* linkPtr, bool addParens);
+        bool findLink(string ref, string* linkPtr, unordered_map<string, Definition*>& map);
+        bool hasWordSpace(string wordSpace) const;
+        void setLink();
+
+        string nextSeparator(const char* start) {
+            fBack2Separator = fPriorSeparator;
+            fPriorSeparator = fSeparator;
+            fEnd = start;
+            return fBack2Separator;
+        }
+
+        const char* nextWord() {
+            fBack2Word = fPriorWord;
+            fPriorWord = fWord;
+            fPriorLink = fLink;
+            return fEnd;
+        }
+
+        bool phraseContinues(string phrase, string* priorWord, string* priorLink) const;
+
+        void setLower() {
+            fAddParens = false;
+            bool allLower = std::all_of(fWord.begin(), fWord.end(), [](char c) {
+                return islower(c);
+            });
+            bool hasParens = fEnd + 2 <= fRefEnd && "()" == string(fEnd, 2);
+            if (hasParens) {
+                if (allLower) {
+                    fWord += "()";
+                    fEnd += 2;
+                }
+            } else if (allLower) {
+                fAddParens = true;
+            }
+        }
+
+        bool setPriorSpaceWord(const char** startPtr) {
+            if (!fPriorSpace) {
+                return false;
+            }
+            string phrase = fPriorWord + fWord;
+            if (this->phraseContinues(phrase, &fPriorWord, &fPriorLink)) {
+                *startPtr = fEnd;
+                return true;
+            }
+            fPriorWord = fPriorWord.substr(0, fPriorWord.length() - 1);
+            return false;
+        }
+
+        void skipParens() {
+            if ("()" == fSeparator.substr(0, 2)) {
+                string funcRef = fPriorWord + "()";
+                if (this->findLink(funcRef, &fPriorLink, false)) {
+                    fPriorWord = funcRef;
+                    fSeparator = fSeparator.substr(2);
+                }
+            }
+        }
+
+        const char* skipWhiteSpace() {
+            const char* start = fSeparatorStart;
+            bool whiteSpace = start < fRefEnd && ' ' >= start[0];
+            while (start < fRefEnd && !isalpha(start[0])) {
+                whiteSpace &= ' ' >= start[0];
+                ++start;
+            }
+            fPriorSpace = false;
+            fSeparator = string(fSeparatorStart, start - fSeparatorStart);
+            if ("" != fPriorWord && whiteSpace) {
+                string wordSpace = fPriorWord + ' ';
+                if (this->hasWordSpace(wordSpace)) {
+                    fPriorWord = wordSpace;
+                    fPriorSpace = true;
+                }
+            }
+            return start;
+        }
+
+        string fRef;
+        string fBack2Word;
+        string fBack2Separator;
+        string fPriorWord;
+        string fPriorLink;
+        string fPriorSeparator;
+        string fWord;
+        string fLink;
+        string fSeparator;
+        string fMethodName;
+        BmhParser* fBmhParser;
+        const NameMap* fNames;
+        const NameMap* fGlobals;
+        const Definition* fLastDef;
+        const Definition* fMethod;
+        const Definition* fSubtopic;
+        const Definition* fPriorDef;
+        const RootDefinition* fRoot;
+        const char* fSeparatorStart;
+        const char* fRefStart;
+        const char* fRefEnd;
+        const char* fEnd;
+        Resolvable fResolvable;
+        bool fAddParens;
+        bool fInMatrix;
+        bool fInProgress;
+        bool fPriorSpace;
+    };
+
     void addCodeBlock(const Definition* def, string& str) const;
     void addPopulators();
-    string addIncludeReferences(const char* refStart, const char* refEnd);
     string addReferences(const char* start, const char* end, Resolvable );
     string anchorDef(string def, string name);
     string anchorLocalRef(string ref, string name);
@@ -57,23 +207,19 @@
     Definition* checkParentsForMatch(Definition* test, string ref) const;
     void childrenOut(Definition* def, const char* contentStart);
     Definition* csParent();
-    bool findLink(string ref, string* link);
-    Definition* findParamType();
+    const Definition* findParamType();
     string getMemberTypeName(const Definition* def, string* memberType);
     static bool HasDetails(const Definition* def);
-    bool hasWordSpace(string ) const;
     void htmlOut(string );
-    Definition* isDefined(const TextParser& , string ref, Resolvable );
-    Definition* isDefinedByParent(RootDefinition* root, string ref);
+    bool isDefined(DefinedState& s);
+    const Definition* isDefined(const TextParser& , Resolvable );
     string linkName(const Definition* ) const;
-    string linkRef(string leadingSpaces, Definition*, string ref, Resolvable );
     void markTypeOut(Definition* , const Definition** prior);
     void mdHeaderOut(int depth) { mdHeaderOutLF(depth, 2); }
     void mdHeaderOutLF(int depth, int lf);
     void parameterHeaderOut(TextParser& paramParser, const Definition** prior, Definition* def);
     void parameterTrailerOut();
     bool parseFromFile(const char* path) override { return true; }
-    bool phraseContinues(string phrase, string* priorWord, string* priorLink) const;
     void populateOne(Definition* def,
             unordered_map<string, RootDefinition::SubtopicContents>& populator);
     void populateTables(const Definition* def, RootDefinition* );
diff --git a/tools/bookmaker/textParser.cpp b/tools/bookmaker/textParser.cpp
index 6ea4a24..0994d7b 100644
--- a/tools/bookmaker/textParser.cpp
+++ b/tools/bookmaker/textParser.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "definition.h"
+#include "textParser.h"
 
 #ifdef SK_BUILD_FOR_WIN
 #include <Windows.h>
diff --git a/tools/bookmaker/textParser.h b/tools/bookmaker/textParser.h
index eee1f7c..ffe5e78 100644
--- a/tools/bookmaker/textParser.h
+++ b/tools/bookmaker/textParser.h
@@ -623,6 +623,24 @@
     trim_end(s);
 }
 
+static inline string trim_inline_spaces(string s) {
+    bool lastSpace = false;
+    string trimmed;
+    for (const char* ptr = &s.front(); ptr <= &s.back(); ++ptr) {
+        char c = *ptr;
+        if (' ' >= c) {
+            if (!lastSpace) {
+                trimmed += ' ';
+            }
+            lastSpace = true;
+            continue;
+        }
+        lastSpace = false;
+        trimmed += c;
+    }
+    return trimmed;
+}
+
 class EscapeParser : public TextParser {
 public:
     EscapeParser(const char* start, const char* end) :