allow bookmaker methods to auto-populate

Adds
#Populate
as markup inside #Method, replacing
the method's description and #Param
and #Return. If present, this info
is retrieved from the include when
writing the web markdown and the
generated include.

TBR=caryclark@google.com

Docs-Preview: https://skia.org/?cl=162820
Bug: skia:
Change-Id: I5df16f227b86651d463e03ddd33849bb127891c0
Reviewed-on: https://skia-review.googlesource.com/c/162820
Commit-Queue: Cary Clark <caryclark@skia.org>
Auto-Submit: Cary Clark <caryclark@skia.org>
Reviewed-by: Cary Clark <caryclark@google.com>
Reviewed-by: Cary Clark <caryclark@skia.org>
diff --git a/docs/SkRect_Reference.bmh b/docs/SkRect_Reference.bmh
index d6dc20d..7571937 100644
--- a/docs/SkRect_Reference.bmh
+++ b/docs/SkRect_Reference.bmh
@@ -246,12 +246,7 @@
 
 #In Constructors
 #Line # constructs from ISize returning (0, 0, width, height) ##
-Returns constructed IRect set to (0, 0, size.width(), size.height()).
-Does not validate input; size.width() or size.height() may be negative.
-
-#Param size  integer values for Rect width and height ##
-
-#Return bounds (0, 0, size.width(), size.height()) ##
+#Populate
 
 #Example
     SkRect rect1 = SkRect::MakeSize({2, 35});
diff --git a/site/user/api/SkColor_Reference.md b/site/user/api/SkColor_Reference.md
index 8a79b54..9ecda8d 100644
--- a/site/user/api/SkColor_Reference.md
+++ b/site/user/api/SkColor_Reference.md
@@ -321,8 +321,8 @@
 ---
 
 <pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-constexpr <a href='#SkAlpha'>SkAlpha</a> <a href='#SK_AlphaOPAQUE'>SK_AlphaOPAQUE</a> = 0xFF;
 constexpr <a href='#SkAlpha'>SkAlpha</a> <a href='#SK_AlphaTRANSPARENT'>SK_AlphaTRANSPARENT</a> = 0x00;
+constexpr <a href='#SkAlpha'>SkAlpha</a> <a href='#SK_AlphaOPAQUE'>SK_AlphaOPAQUE</a> = 0xFF;
 </pre>
 
 <a href='#Alpha'>Alpha</a> constants are conveniences to represent fully transparent and fully
@@ -372,17 +372,17 @@
 
 <pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
 constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorGREEN'>SK_ColorGREEN</a>;
-constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorLTGRAY'>SK_ColorLTGRAY</a>;
 constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorRED'>SK_ColorRED</a>;
-constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorGRAY'>SK_ColorGRAY</a>;
-constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorYELLOW'>SK_ColorYELLOW</a>;
 constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorBLACK'>SK_ColorBLACK</a>;
-constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorDKGRAY'>SK_ColorDKGRAY</a>;
 constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorTRANSPARENT'>SK_ColorTRANSPARENT</a>;
-constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorCYAN'>SK_ColorCYAN</a>;
+constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorDKGRAY'>SK_ColorDKGRAY</a>;
+constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorLTGRAY'>SK_ColorLTGRAY</a>;
+constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorGRAY'>SK_ColorGRAY</a>;
 constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorWHITE'>SK_ColorWHITE</a>;
-constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorMAGENTA'>SK_ColorMAGENTA</a>;
+constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorYELLOW'>SK_ColorYELLOW</a>;
 constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorBLUE'>SK_ColorBLUE</a>;
+constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorCYAN'>SK_ColorCYAN</a>;
+constexpr <a href='#SkColor'>SkColor</a> <a href='#SK_ColorMAGENTA'>SK_ColorMAGENTA</a>;
 </pre>
 
 <a href='#Color'>Color</a> names are provided as conveniences, but are not otherwise special.
diff --git a/site/user/api/SkMatrix_Reference.md b/site/user/api/SkMatrix_Reference.md
index bb0feaf..a94b565 100644
--- a/site/user/api/SkMatrix_Reference.md
+++ b/site/user/api/SkMatrix_Reference.md
@@ -790,15 +790,15 @@
 ---
 
 <pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-    static constexpr int <a href='#SkMatrix_kMPersp2'>kMPersp2</a> = 8;
-    static constexpr int <a href='#SkMatrix_kMPersp0'>kMPersp0</a> = 6;
     static constexpr int <a href='#SkMatrix_kMPersp1'>kMPersp1</a> = 7;
-    static constexpr int <a href='#SkMatrix_kMSkewX'>kMSkewX</a> = 1;
+    static constexpr int <a href='#SkMatrix_kMPersp2'>kMPersp2</a> = 8;
+    static constexpr int <a href='#SkMatrix_kMSkewY'>kMSkewY</a> = 3;
     static constexpr int <a href='#SkMatrix_kMTransX'>kMTransX</a> = 2;
     static constexpr int <a href='#SkMatrix_kMScaleX'>kMScaleX</a> = 0;
-    static constexpr int <a href='#SkMatrix_kMSkewY'>kMSkewY</a> = 3;
-    static constexpr int <a href='#SkMatrix_kMScaleY'>kMScaleY</a> = 4;
+    static constexpr int <a href='#SkMatrix_kMSkewX'>kMSkewX</a> = 1;
     static constexpr int <a href='#SkMatrix_kMTransY'>kMTransY</a> = 5;
+    static constexpr int <a href='#SkMatrix_kMScaleY'>kMScaleY</a> = 4;
+    static constexpr int <a href='#SkMatrix_kMPersp0'>kMPersp0</a> = 6;
 </pre>
 
 <a href='#Matrix'>Matrix</a> organizes its values in row order. These members correspond to
diff --git a/site/user/api/SkRect_Reference.md b/site/user/api/SkRect_Reference.md
index b4d170e..4129b38 100644
--- a/site/user/api/SkRect_Reference.md
+++ b/site/user/api/SkRect_Reference.md
@@ -402,13 +402,13 @@
 static <a href='#SkRect'>SkRect</a> <a href='#SkRect_Make'>Make</a>(const <a href='undocumented#SkISize'>SkISize</a>& size)
 </pre>
 
-Returns constructed <a href='SkIRect_Reference#IRect'>IRect</a> set to (0, 0, <a href='#SkRect_Make_size'>size</a>.<a href='#SkRect_width'>width</a>(), <a href='#SkRect_Make_size'>size</a>.<a href='#SkRect_height'>height</a>()).
+Returns constructed <a href='SkIRect_Reference#SkIRect'>SkIRect</a> set to (0, 0, <a href='#SkRect_Make_size'>size</a>.<a href='#SkRect_width'>width</a>(), <a href='#SkRect_Make_size'>size</a>.<a href='#SkRect_height'>height</a>()).
 Does not validate input; <a href='#SkRect_Make_size'>size</a>.<a href='#SkRect_width'>width</a>() or <a href='#SkRect_Make_size'>size</a>.<a href='#SkRect_height'>height</a>() may be negative.
 
 ### Parameters
 
 <table>  <tr>    <td><a name='SkRect_Make_size'><code><strong>size</strong></code></a></td>
-    <td>integer values for <a href='#Rect'>Rect</a> width and height</td>
+    <td>integer values for <a href='#SkRect'>SkRect</a> width and height</td>
   </tr>
 </table>
 
diff --git a/tools/bookmaker/bookmaker.cpp b/tools/bookmaker/bookmaker.cpp
index a4968ef..7f0b9f9 100644
--- a/tools/bookmaker/bookmaker.cpp
+++ b/tools/bookmaker/bookmaker.cpp
@@ -147,7 +147,7 @@
 , { "",             MarkType::kPhraseParam,  R_Y, E_N, 0 }
 , { "",             MarkType::kPhraseRef,    R_N, E_N, 0 }
 , { "Platform",     MarkType::kPlatform,     R_N, E_N, M(Example) | M(NoExample) }
-, { "Populate",     MarkType::kPopulate,     R_N, E_N, M(Code) }
+, { "Populate",     MarkType::kPopulate,     R_N, E_N, M(Code) | M(Method) }
 , { "Private",      MarkType::kPrivate,      R_N, E_N, M_CSST | M_MDCM | M_E }
 , { "Return",       MarkType::kReturn,       R_Y, E_N, M(Method) }
 , { "",             MarkType::kRow,          R_Y, E_N, M(Table) | M(List) }
diff --git a/tools/bookmaker/bookmaker.h b/tools/bookmaker/bookmaker.h
index 160b715..950bad7 100644
--- a/tools/bookmaker/bookmaker.h
+++ b/tools/bookmaker/bookmaker.h
@@ -959,7 +959,7 @@
     string formatFunction(Format format) const;
     const Definition* hasChild(MarkType markType) const;
     bool hasMatch(string name) const;
-    const Definition* hasParam(string ref) const;
+    Definition* hasParam(string ref);
     string incompleteMessage(DetailsType ) const;
     bool isClone() const { return fClone; }
 
@@ -1765,6 +1765,7 @@
     Definition* findIncludeObject(const Definition& includeDef, MarkType markType,
                                   string typeName);
     static KeyWord FindKey(const char* start, const char* end);
+    Definition* findMethod(const Definition& bmhDef);
     Bracket grandParentBracket() const;
     const Definition* include(string ) const;
     bool isClone(const Definition& token);
@@ -2439,17 +2440,19 @@
     Definition* checkParentsForMatch(Definition* test, string ref) const;
     void childrenOut(Definition* def, const char* contentStart);
     Definition* csParent();
-    const Definition* findParamType();
+    Definition* findParamType();
     string getMemberTypeName(const Definition* def, string* memberType);
     static bool HasDetails(const Definition* def);
     void htmlOut(string );
-    const Definition* isDefined(const TextParser& , string ref, BmhParser::Resolvable );
-    const Definition* isDefinedByParent(RootDefinition* root, string ref);
+    Definition* isDefined(const TextParser& , string ref, BmhParser::Resolvable );
+    Definition* isDefinedByParent(RootDefinition* root, string ref);
     string linkName(const Definition* ) const;
-    string linkRef(string leadingSpaces, const Definition*, string ref, BmhParser::Resolvable );
+    string linkRef(string leadingSpaces, Definition*, string ref, BmhParser::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; }
     void populateOne(Definition* def,
             unordered_map<string, RootDefinition::SubtopicContents>& populator);
@@ -2498,6 +2501,7 @@
     }
 
     void resolveOut(const char* start, const char* end, BmhParser::Resolvable );
+    void returnHeaderOut(const Definition** prior, Definition* def);
     void rowOut(string col1, const Definition* col2);
     void rowOut(const char * name, string description, bool literalName);
 
diff --git a/tools/bookmaker/definition.cpp b/tools/bookmaker/definition.cpp
index 4f841e4..52620f8 100644
--- a/tools/bookmaker/definition.cpp
+++ b/tools/bookmaker/definition.cpp
@@ -467,9 +467,11 @@
     bool expectReturn = this->methodHasReturn(name, &methodParser);
     bool foundReturn = false;
     bool foundException = false;
+    bool foundPopulate = false;
     for (auto& child : fChildren) {
         foundException |= MarkType::kDeprecated == child->fMarkType
                 || MarkType::kExperimental == child->fMarkType;
+        foundPopulate |= MarkType::kPopulate == child->fMarkType;
         if (MarkType::kReturn != child->fMarkType) {
             if (MarkType::kParam == child->fMarkType) {
                 child->fVisited = false;
@@ -484,7 +486,7 @@
         }
         foundReturn = true;
     }
-    if (expectReturn && !foundReturn && !foundException) {
+    if (expectReturn && !foundReturn && !foundException && !foundPopulate) {
         return methodParser.reportError<bool>("missing #Return marker");
     }
     const char* paren = methodParser.strnchr('(', methodParser.fEnd);
@@ -518,7 +520,7 @@
             foundParam = true;
 
         }
-        if (!foundParam && !foundException) {
+        if (!foundParam && !foundException && !foundPopulate) {
             return methodParser.reportError<bool>("no #Param found");
         }
         if (')' == nextEnd[0]) {
@@ -587,7 +589,8 @@
         priorDef = nullptr;
     }
     if (!descEnd) {
-        return incomplete ? true : methodParser.reportError<bool>("missing description");
+        return incomplete || foundPopulate ? true :
+                methodParser.reportError<bool>("missing description");
     }
     TextParser description(fFileName, descStart, descEnd, fLineCount);
     // expect first word capitalized and pluralized. expect a trailing period
@@ -891,7 +894,7 @@
     return nullptr;
 }
 
-const Definition* Definition::hasParam(string ref) const {
+Definition* Definition::hasParam(string ref) {
     SkASSERT(MarkType::kMethod == fMarkType);
     for (auto iter : fChildren) {
         if (MarkType::kParam != iter->fMarkType) {
@@ -900,7 +903,18 @@
         if (iter->fName == ref) {
             return &*iter;
         }
-
+    }
+    for (auto& iter : fTokens) {
+        if (MarkType::kComment != iter.fMarkType) {
+            continue;
+        }
+        TextParser parser(&iter);
+        if (!parser.skipExact("@param ")) {
+            continue;
+        }
+        if (parser.skipExact(ref.c_str())) {
+            return &iter;
+        }
     }
     return nullptr;
 }
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp
index ad77119..0716bc7 100644
--- a/tools/bookmaker/includeParser.cpp
+++ b/tools/bookmaker/includeParser.cpp
@@ -1844,6 +1844,21 @@
     return &markupDef;
 }
 
+Definition* IncludeParser::findMethod(const Definition& bmhDef) {
+    auto doubleColon = bmhDef.fName.find("::");
+    SkASSERT(string::npos != doubleColon);  // more work to do to support global refs
+    string className = bmhDef.fName.substr(0, doubleColon);
+    const auto& iClass = fIClassMap.find(className);
+    SkASSERT(fIClassMap.end() != iClass);
+    string methodName = bmhDef.fName.substr(doubleColon + 2);
+    auto& iTokens = iClass->second.fTokens;
+    const auto& iMethod = std::find_if(iTokens.begin(), iTokens.end(),
+            [methodName](Definition& token) {
+            return MarkType::kMethod == token.fMarkType && methodName == token.fName; } );
+    SkASSERT(iTokens.end() != iMethod);
+    return &*iMethod;
+}
+
 Definition* IncludeParser::parentBracket(Definition* parent) const {
     while (parent && Definition::Type::kBracket != parent->fType) {
         parent = parent->fParent;
diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp
index 383a6a5..768ccf6 100644
--- a/tools/bookmaker/includeWriter.cpp
+++ b/tools/bookmaker/includeWriter.cpp
@@ -890,46 +890,67 @@
         this->indentIn(IndentKind::kMethodOut);
         fIndentNext = false;
     }
-    this->writeCommentHeader();
-    fIndent += 4;
-    this->descriptionOut(method, SkipFirstLine::kNo, Phrase::kNo);
-    // compute indention column
-    size_t column = 0;
-    bool hasParmReturn = false;
-    for (auto methodPart : method->fChildren) {
-        if (MarkType::kParam == methodPart->fMarkType) {
-            column = SkTMax(column, methodPart->fName.length());
-            hasParmReturn = true;
-        } else if (MarkType::kReturn == methodPart->fMarkType) {
-            hasParmReturn = true;
+    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 = false);
+        while (--commentIndex >= 0) {
+            std::advance(iter, -1);
+            if (Bracket::kSlashStar == iter->fBracket) {
+                SkASSERT(sawMethod);
+                break;
+            }
+            SkASSERT(!sawMethod);
+            SkDEBUGCODE(sawMethod = MarkType::kMethod == iter->fMarkType);
         }
-    }
-    if (hasParmReturn) {
         this->lf(2);
-        column += fIndent + sizeof("@return ");
-        int saveIndent = fIndent;
+        this->writeString("/");
+        this->writeBlock(iter->length(), iter->fContentStart);
+        this->lfcr();
+    } else {
+        this->writeCommentHeader();
+        fIndent += 4;
+        this->descriptionOut(method, SkipFirstLine::kNo, Phrase::kNo);
+        // compute indention column
+        size_t column = 0;
+        bool hasParmReturn = false;
         for (auto methodPart : method->fChildren) {
             if (MarkType::kParam == methodPart->fMarkType) {
-                this->writeString("@param");
-                this->writeSpace();
-                this->writeString(methodPart->fName.c_str());
+                column = SkTMax(column, methodPart->fName.length());
+                hasParmReturn = true;
             } else if (MarkType::kReturn == methodPart->fMarkType) {
-                this->writeString("@return");
-            } else {
-                continue;
+                hasParmReturn = true;
             }
-            this->indentToColumn(column);
-            fIndent = column;
-            this->descriptionOut(methodPart, SkipFirstLine::kNo, Phrase::kYes);
-            fIndent = saveIndent;
+        }
+        if (hasParmReturn) {
+            this->lf(2);
+            column += fIndent + sizeof("@return ");
+            int saveIndent = fIndent;
+            for (auto methodPart : method->fChildren) {
+                if (MarkType::kParam == methodPart->fMarkType) {
+                    this->writeString("@param");
+                    this->writeSpace();
+                    this->writeString(methodPart->fName.c_str());
+                } else if (MarkType::kReturn == methodPart->fMarkType) {
+                    this->writeString("@return");
+                } else {
+                    continue;
+                }
+                this->indentToColumn(column);
+                fIndent = column;
+                this->descriptionOut(methodPart, SkipFirstLine::kNo, Phrase::kYes);
+                fIndent = saveIndent;
+                this->lfcr();
+            }
+        } else {
             this->lfcr();
         }
-    } else {
+        fIndent -= 4;
         this->lfcr();
+        this->writeCommentTrailer(OneLine::kNo);
     }
-    fIndent -= 4;
-    this->lfcr();
-    this->writeCommentTrailer(OneLine::kNo);
     fBmhMethod = nullptr;
     fMethodDef = nullptr;
     fEnumDef = nullptr;
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp
index 0f322b0..7cca263 100644
--- a/tools/bookmaker/mdOut.cpp
+++ b/tools/bookmaker/mdOut.cpp
@@ -440,7 +440,7 @@
         if (BmhParser::Resolvable::kCode == resolvable) {
             fixup_const_function_name(&ref);
         }
-        if (const Definition* def = this->isDefined(t, ref, resolvable)) {
+        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);
@@ -465,7 +465,7 @@
                 // otherwise flag as error
                 int suffix = '2';
                 bool foundMatch = false;
-                const Definition* altDef = def;
+                Definition* altDef = def;
                 while (altDef && suffix <= '9') {
                     if ((foundMatch = altDef->paramsMatch(fullRef, ref))) {
                         def = altDef;
@@ -523,7 +523,7 @@
             }
             t.next();
             ref = string(start, t.fChar - start);
-            if (const Definition* def = this->isDefined(t, ref, BmhParser::Resolvable::kYes)) {
+            if (Definition* def = this->isDefined(t, ref, BmhParser::Resolvable::kYes)) {
                 SkASSERT(def->fFiddle.length());
 				result += linkRef(leadingSpaces, def, ref, resolvable);
                 continue;
@@ -554,7 +554,7 @@
             // 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
-            const Definition* def = nullptr;
+            Definition* def = nullptr;
             if (fMethod && (def = fMethod->hasParam(ref))) {
 				result += linkRef(leadingSpaces, def, ref, resolvable);
                 fLastParam = def;
@@ -567,7 +567,7 @@
 //                        || '.' != t.backup(ref.c_str())
                         && ('k' != ref[0] && string::npos == ref.find("_Private"))) {
                     if ('.' == wordStart[0] && (distFromParam >= 1 && distFromParam <= 16)) {
-                        const Definition* paramType = this->findParamType();
+                        Definition* paramType = this->findParamType();
                         if (paramType) {
                             string fullName = paramType->fName + "::" + ref;
                             if (paramType->hasMatch(fullName)) {
@@ -970,7 +970,7 @@
     return csParent;
 }
 
-const Definition* MdOut::findParamType() {
+Definition* MdOut::findParamType() {
     SkASSERT(fMethod);
     TextParser parser(fMethod->fFileName, fMethod->fStart, fMethod->fContentStart,
             fMethod->fLineCount);
@@ -985,7 +985,7 @@
         SkASSERT(!parser.eof());
         string name = string(word, parser.fChar - word);
         if (fLastParam->fName == name) {
-            const Definition* paramType = this->isDefined(parser, lastFull,
+            Definition* paramType = this->isDefined(parser, lastFull,
                     BmhParser::Resolvable::kOut);
             return paramType;
         }
@@ -1044,11 +1044,11 @@
     FPRINTF("%s", s.c_str());
 }
 
-const Definition* MdOut::isDefinedByParent(RootDefinition* root, string ref) {
+Definition* MdOut::isDefinedByParent(RootDefinition* root, string ref) {
     if (ref == root->fName) {
         return root;
     }
-    if (const Definition* definition = root->find(ref, RootDefinition::AllowParens::kYes)) {
+    if (Definition* definition = root->find(ref, RootDefinition::AllowParens::kYes)) {
         return definition;
     }
     Definition* test = root;
@@ -1068,7 +1068,7 @@
             if (ref == leaf.first) {
                 return leaf.second;
             }
-            const Definition* definition = leaf.second->find(ref,
+            Definition* definition = leaf.second->find(ref,
                     RootDefinition::AllowParens::kYes);
             if (definition) {
                 return definition;
@@ -1096,7 +1096,7 @@
     return nullptr;
 }
 
-const Definition* MdOut::isDefined(const TextParser& parser, string ref,
+Definition* MdOut::isDefined(const TextParser& parser, string ref,
         BmhParser::Resolvable resolvable) {
     auto rootIter = fBmhParser.fClassMap.find(ref);
     if (rootIter != fBmhParser.fClassMap.end()) {
@@ -1126,15 +1126,15 @@
     if (defineIter != fBmhParser.fDefineMap.end()) {
         return &defineIter->second;
     }
-    for (const auto& external : fBmhParser.fExternals) {
+    for (auto& external : fBmhParser.fExternals) {
         if (external.fName == ref) {
             return &external;
         }
     }
-    if (const Definition* definition = this->isDefinedByParent(fRoot, ref)) {
+    if (Definition* definition = this->isDefinedByParent(fRoot, ref)) {
         return definition;
     }
-    if (const Definition* definition = this->isDefinedByParent(fSubtopic, ref)) {
+    if (Definition* definition = this->isDefinedByParent(fSubtopic, ref)) {
         return definition;
     }
     size_t doubleColon = ref.find("::");
@@ -1143,7 +1143,7 @@
         auto classIter = fBmhParser.fClassMap.find(className);
         if (classIter != fBmhParser.fClassMap.end()) {
             RootDefinition& classDef = classIter->second;
-            const Definition* result = classDef.find(ref, RootDefinition::AllowParens::kYes);
+            Definition* result = classDef.find(ref, RootDefinition::AllowParens::kYes);
             if (result) {
                 return result;
             }
@@ -1202,14 +1202,14 @@
             string className(ref, 0, pos);
             auto classIter = fBmhParser.fClassMap.find(className);
             if (classIter != fBmhParser.fClassMap.end()) {
-                if (const Definition* definition = classIter->second.find(ref,
+                if (Definition* definition = classIter->second.find(ref,
                         RootDefinition::AllowParens::kYes)) {
                     return definition;
                 }
             }
             auto enumIter = fBmhParser.fEnumMap.find(className);
             if (enumIter != fBmhParser.fEnumMap.end()) {
-                if (const Definition* definition = enumIter->second.find(ref,
+                if (Definition* definition = enumIter->second.find(ref,
                         RootDefinition::AllowParens::kYes)) {
                     return definition;
                 }
@@ -1242,13 +1242,20 @@
 
 // for now, hard-code to html links
 // def should not include SkXXX_
-string MdOut::linkRef(string leadingSpaces, const Definition* def,
+string MdOut::linkRef(string leadingSpaces, Definition* def,
         string ref, BmhParser::Resolvable resolvable) {
     string buildup;
     string refName;
     const string* str = &def->fFiddle;
-    SkASSERT(str->length() > 0);
     string classPart = *str;
+    bool fromInclude = "" == classPart;
+    if (fromInclude) {
+        const Definition* parent = def->csParent();
+        SkASSERT(parent);
+        classPart = parent->fName;
+        refName = classPart + '_' + def->fParent->fName + '_' + ref;
+    }
+    SkASSERT(classPart.length() > 0);
     bool globalEnumMember = false;
     if (MarkType::kAlias == def->fMarkType) {
         def = def->fParent;
@@ -1265,18 +1272,22 @@
     } else if (MarkType::kTopic == def->fMarkType) {
         refName = def->fName;
     } else {
-        if ('k' == (*str)[0] && string::npos != str->find("_Sk")) {
+        if ('k' == classPart[0] && string::npos != classPart.find("_Sk")) {
             globalEnumMember = true;
         } else {
-            SkASSERT("Sk" == str->substr(0, 2) || "SK" == str->substr(0, 2)
+            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 = str->find('_');
-            classPart = string::npos != under ? str->substr(0, under) : *str;
+            size_t under = classPart.find('_');
+            if (string::npos != under) {
+                classPart = classPart.substr(0, under);
+            }
         }
-        refName = def->fFiddle;
+        if (!fromInclude) {
+            refName = def->fFiddle;
+        }
     }
-    bool classMatch = fRoot->fFileName == def->fFileName;
+    bool classMatch = fRoot->fFileName == def->fFileName || fromInclude;
     SkASSERT(fRoot);
     SkASSERT(fRoot->fFileName.length());
     if (!classMatch) {
@@ -1751,41 +1762,13 @@
         case MarkType::kOutdent:
             break;
         case MarkType::kParam: {
-            if (TableState::kNone == fTableState) {
-                SkASSERT(!*prior || MarkType::kParam != (*prior)->fMarkType);
-                this->mdHeaderOut(3);
-                this->htmlOut(
-                        "Parameters\n"
-                        "\n"
-                        "<table>"
-                        );
-                this->lf(1);
-                fTableState = TableState::kRow;
-            }
-            if (TableState::kRow == fTableState) {
-                FPRINTF("  <tr>");
-                this->lf(1);
-                fTableState = TableState::kColumn;
-            }
             TextParser paramParser(def->fFileName, def->fStart, def->fContentStart,
                     def->fLineCount);
             paramParser.skipWhiteSpace();
             SkASSERT(paramParser.startsWith("#Param"));
             paramParser.next(); // skip hash
             paramParser.skipToNonName(); // skip Param
-            paramParser.skipSpace();
-            const char* paramName = paramParser.fChar;
-            paramParser.skipToSpace();
-            string paramNameStr(paramName, (int) (paramParser.fChar - paramName));
-            if (!this->checkParamReturnBody(def)) {
-                *prior = def;
-                return;
-            }
-            string refNameStr = def->fParent->fFiddle + "_" + paramNameStr;
-            this->htmlOut("    <td>" + anchorDef(refNameStr,
-                    "<code><strong>" + paramNameStr + "</strong></code>") + "</td>");
-            this->lfAlways(1);
-            FPRINTF("    <td>");
+            this->parameterHeaderOut(paramParser, prior, def);
         } break;
         case MarkType::kPhraseDef:
             // skip text and children
@@ -1851,58 +1834,101 @@
             break;
         case MarkType::kPopulate: {
             Definition* parent = def->fParent;
-            SkASSERT(parent && MarkType::kCode == parent->fMarkType);
-            auto inDef = std::find_if(parent->fChildren.begin(), parent->fChildren.end(),
-                    [](const Definition* child) { return MarkType::kIn == child->fMarkType; });
-            if (parent->fChildren.end() != inDef) {
-                auto filterDef = std::find_if(parent->fChildren.begin(), parent->fChildren.end(),
-                        [](const Definition* child) { return MarkType::kFilter == child->fMarkType; });
-                SkASSERT(parent->fChildren.end() != filterDef);
-                string codeBlock = fIncludeParser.filteredBlock(
-                        string((*inDef)->fContentStart, (*inDef)->length()),
-                        string((*filterDef)->fContentStart, (*filterDef)->length()));
-                this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
-                        this->resolvable(parent));
-                break;
-            }
-            // find include matching code parent
-            Definition* grand = parent->fParent;
-            SkASSERT(grand);
-            if (MarkType::kClass == grand->fMarkType
-                    || MarkType::kStruct == grand->fMarkType
-                    || MarkType::kEnum == grand->fMarkType
-                    || MarkType::kEnumClass == grand->fMarkType
-                    || MarkType::kTypedef == grand->fMarkType
-                    || MarkType::kDefine == grand->fMarkType) {
-                string codeBlock = fIncludeParser.codeBlock(*grand, fInProgress);
-                this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
-                        this->resolvable(parent));
-            } else if (MarkType::kTopic == grand->fMarkType) {
-                // use bmh file name to find include file name
-                size_t start = grand->fFileName.rfind("Sk");
-                SkASSERT(start != string::npos);
-                size_t end = grand->fFileName.rfind("_Reference");
-                SkASSERT(end != string::npos && end > start);
-                string incName(grand->fFileName.substr(start, end - start));
-                const Definition* includeDef = fIncludeParser.include(incName + ".h");
-                SkASSERT(includeDef);
-                string codeBlock;
-                this->addCodeBlock(includeDef, codeBlock);
-                this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
-                        this->resolvable(parent));
+            SkASSERT(parent);
+            if (MarkType::kCode == parent->fMarkType) {
+                auto inDef = std::find_if(parent->fChildren.begin(), parent->fChildren.end(),
+                        [](const Definition* child) { return MarkType::kIn == child->fMarkType; });
+                if (parent->fChildren.end() != inDef) {
+                    auto filterDef = std::find_if(parent->fChildren.begin(),
+                            parent->fChildren.end(), [](const Definition* child) {
+                            return MarkType::kFilter == child->fMarkType; });
+                    SkASSERT(parent->fChildren.end() != filterDef);
+                    string codeBlock = fIncludeParser.filteredBlock(
+                            string((*inDef)->fContentStart, (*inDef)->length()),
+                            string((*filterDef)->fContentStart, (*filterDef)->length()));
+                    this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
+                            this->resolvable(parent));
+                    break;
+                }
+                // find include matching code parent
+                Definition* grand = parent->fParent;
+                SkASSERT(grand);
+                if (MarkType::kClass == grand->fMarkType
+                        || MarkType::kStruct == grand->fMarkType
+                        || MarkType::kEnum == grand->fMarkType
+                        || MarkType::kEnumClass == grand->fMarkType
+                        || MarkType::kTypedef == grand->fMarkType
+                        || MarkType::kDefine == grand->fMarkType) {
+                    string codeBlock = fIncludeParser.codeBlock(*grand, fInProgress);
+                    this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
+                            this->resolvable(parent));
+                } else if (MarkType::kTopic == grand->fMarkType) {
+                    // use bmh file name to find include file name
+                    size_t start = grand->fFileName.rfind("Sk");
+                    SkASSERT(start != string::npos);
+                    size_t end = grand->fFileName.rfind("_Reference");
+                    SkASSERT(end != string::npos && end > start);
+                    string incName(grand->fFileName.substr(start, end - start));
+                    const Definition* includeDef = fIncludeParser.include(incName + ".h");
+                    SkASSERT(includeDef);
+                    string codeBlock;
+                    this->addCodeBlock(includeDef, codeBlock);
+                    this->resolveOut(codeBlock.c_str(), codeBlock.c_str() + codeBlock.length(),
+                            this->resolvable(parent));
+                } else {
+                    SkASSERT(MarkType::kSubtopic == grand->fMarkType);
+                    auto inTag = std::find_if(grand->fChildren.begin(), grand->fChildren.end(),
+                            [](Definition* child){return MarkType::kIn == child->fMarkType;});
+                    SkASSERT(grand->fChildren.end() != inTag);
+                    auto filterTag = std::find_if(grand->fChildren.begin(), grand->fChildren.end(),
+                            [](Definition* child){return MarkType::kFilter == child->fMarkType;});
+                    SkASSERT(grand->fChildren.end() != filterTag);
+                    string inContents((*inTag)->fContentStart, (*inTag)->length());
+                    string filterContents((*filterTag)->fContentStart, (*filterTag)->length());
+                    string filteredBlock = fIncludeParser.filteredBlock(inContents, filterContents);
+                    this->resolveOut(filteredBlock.c_str(), filteredBlock.c_str()
+                            + filteredBlock.length(), this->resolvable(parent));
+                }
             } else {
-                SkASSERT(MarkType::kSubtopic == grand->fMarkType);
-                auto inTag = std::find_if(grand->fChildren.begin(), grand->fChildren.end(),
-                        [](Definition* child){return MarkType::kIn == child->fMarkType;});
-                SkASSERT(grand->fChildren.end() != inTag);
-                auto filterTag = std::find_if(grand->fChildren.begin(), grand->fChildren.end(),
-                        [](Definition* child){return MarkType::kFilter == child->fMarkType;});
-                SkASSERT(grand->fChildren.end() != filterTag);
-                string inContents((*inTag)->fContentStart, (*inTag)->length());
-                string filterContents((*filterTag)->fContentStart, (*filterTag)->length());
-                string filteredBlock = fIncludeParser.filteredBlock(inContents, filterContents);
-                this->resolveOut(filteredBlock.c_str(), filteredBlock.c_str()
-                        + filteredBlock.length(), this->resolvable(parent));
+                SkASSERT(MarkType::kMethod == parent->fMarkType);
+                // retrieve parameters, return, description from include
+                Definition* iMethod = fIncludeParser.findMethod(*parent);
+                SkDebugf("");
+                bool wroteParam = false;
+                fMethod = iMethod;
+                for (auto& entry : iMethod->fTokens) {
+                    if (MarkType::kComment != entry.fMarkType) {
+                        continue;
+                    }
+                    TextParser parser(&entry);
+                    if (parser.skipExact("@param ")) { // write parameters, if any
+                        this->parameterHeaderOut(parser, prior, def);
+                        this->resolveOut(parser.fChar, parser.fEnd, BmhParser::Resolvable::kYes);
+                        this->parameterTrailerOut();
+                        wroteParam = true;
+                        continue;
+                    }
+                    if (wroteParam) {
+                        this->writePending();
+                        FPRINTF("</table>");
+                        this->lf(2);
+                        fTableState = TableState::kNone;
+                        wroteParam = false;
+                    }
+                    if (parser.skipExact("@return ")) { // write return, if any
+                        this->returnHeaderOut(prior, def);
+                        this->resolveOut(parser.fChar, parser.fEnd, BmhParser::Resolvable::kYes);
+                        this->lf(2);
+                        continue;
+                    }
+                    if (1 == entry.length() && '/' == entry.fContentStart[0]) {
+                        continue;
+                    }
+                    this->resolveOut(entry.fContentStart, entry.fContentEnd,
+                            BmhParser::Resolvable::kYes);  // write description
+                    this->lf(1);
+                }
+                fMethod = nullptr;
             }
             } break;
         case MarkType::kPrivate:
@@ -1912,13 +1938,7 @@
             this->lf(2);
             break;
         case MarkType::kReturn:
-            this->mdHeaderOut(3);
-            FPRINTF("Return Value");
-            if (!this->checkParamReturnBody(def)) {
-                *prior = def;
-                return;
-            }
-            this->lf(2);
+            this->returnHeaderOut(prior, def);
             break;
         case MarkType::kRow:
             if (fInList) {
@@ -2142,13 +2162,7 @@
                 lookForOneLiner = false;
             }
         case MarkType::kParam:
-            SkASSERT(TableState::kColumn == fTableState);
-            fTableState = TableState::kRow;
-            this->writePending();
-            FPRINTF("</td>");
-            this->lfAlways(1);
-            FPRINTF("  </tr>");
-            this->lfAlways(1);
+            this->parameterTrailerOut();
             break;
         case MarkType::kReturn:
         case MarkType::kSeeAlso:
@@ -2195,6 +2209,48 @@
     FPRINTF(" ");
 }
 
+void MdOut::parameterHeaderOut(TextParser& paramParser, const Definition** prior, Definition* def) {
+    if (TableState::kNone == fTableState) {
+        SkASSERT(!*prior || MarkType::kParam != (*prior)->fMarkType);
+        this->mdHeaderOut(3);
+        this->htmlOut(
+                "Parameters\n"
+                "\n"
+                "<table>"
+                );
+        this->lf(1);
+        fTableState = TableState::kRow;
+    }
+    if (TableState::kRow == fTableState) {
+        FPRINTF("  <tr>");
+        this->lf(1);
+        fTableState = TableState::kColumn;
+    }
+    paramParser.skipSpace();
+    const char* paramName = paramParser.fChar;
+    paramParser.skipToSpace();
+    string paramNameStr(paramName, (int) (paramParser.fChar - paramName));
+    if (MarkType::kPopulate != def->fMarkType && !this->checkParamReturnBody(def)) {
+        *prior = def;
+        return;
+    }
+    string refNameStr = def->fParent->fFiddle + "_" + paramNameStr;
+    this->htmlOut("    <td>" + this->anchorDef(refNameStr,
+            "<code><strong>" + paramNameStr + "</strong></code>") + "</td>");
+    this->lfAlways(1);
+    FPRINTF("    <td>");
+}
+
+void MdOut::parameterTrailerOut() {
+    SkASSERT(TableState::kColumn == fTableState);
+    fTableState = TableState::kRow;
+    this->writePending();
+    FPRINTF("</td>");
+    this->lfAlways(1);
+    FPRINTF("  </tr>");
+    this->lfAlways(1);
+}
+
 void MdOut::populateOne(Definition* def,
         unordered_map<string, RootDefinition::SubtopicContents>& populator) {
     if (MarkType::kConst == def->fMarkType) {
@@ -2370,6 +2426,16 @@
     }
 }
 
+void MdOut::returnHeaderOut(const Definition** prior, Definition* def) {
+    this->mdHeaderOut(3);
+    FPRINTF("Return Value");
+    if (MarkType::kPopulate != def->fMarkType && !this->checkParamReturnBody(def)) {
+        *prior = def;
+        return;
+    }
+    this->lf(2);
+}
+
 void MdOut::rowOut(string col1, const Definition* col2) {
     FPRINTF("%s", fOddRow ? kTR_Dark.c_str() : "  <tr>");
     this->lfAlways(1);