Publish docs for rrect, picture, blendmode

Complete basic docs for SkRRect, SkPicture, SkBlendMode.
Add a new rule that checks the main description tense.
Check for spelling errors.

R=caryclark@google.com

Docs-Preview: https://skia.org/?cl=138542
Bug: skia:6898
Change-Id: Iba547873775a89f1d652be9b0219b84ffa8d0628
Reviewed-on: https://skia-review.googlesource.com/138542
Commit-Queue: Cary Clark <caryclark@skia.org>
Reviewed-by: Cary Clark <caryclark@skia.org>
Auto-Submit: Cary Clark <caryclark@skia.org>
diff --git a/tools/bookmaker/bookmaker.cpp b/tools/bookmaker/bookmaker.cpp
index 15598be..65f89f5 100644
--- a/tools/bookmaker/bookmaker.cpp
+++ b/tools/bookmaker/bookmaker.cpp
@@ -48,10 +48,9 @@
 deprecated methods should be sorted down in md out, and show include "Deprecated." text body.
 rewrap text to fit in some number of columns
 #Literal is inflexible, making the entire #Code block link-less (see $Literal in SkImageInfo)
-     would rather keep links for boby above #Literal, and/or make it a block and not a one-liner
+     would rather keep links for body above #Literal, and/or make it a block and not a one-liner
 add check to require #Const to contain #Code block if defining const or constexpr (enum consts have
-     #Code blocks inside the #Enum def
-add spelling rule to look for x-bit but allow x bits
+     #Code blocks inside the #Enum def)
 
 There are a number of formatting bugs with ad hoc patches where a substitution doesn't keep
 the space before or after, or the linefeeds before or after. The rules are not very good either.
@@ -72,8 +71,6 @@
 phrase def. Could put it in the token list instead I guess, or make a definition subclass used
 by phrase def with an additional slot...
 
-
-
 #Deprecated soon
 ##
 should emit the text "To be deprecated soon." (right now you get just "soon")
@@ -86,9 +83,8 @@
 Or, only allow #Line and moderate text description in #Const. Put more verbose text, example,
 seealso, in subsequent #SubTopic. Alpha_Type does this and it looks good.
 
-more spelling: x-value y-value
-
 see head of selfCheck.cpp for additional todos
+see head of spellCheck.cpp for additional todos
  */
 
 /*
diff --git a/tools/bookmaker/definition.cpp b/tools/bookmaker/definition.cpp
index 18ce808..056e4f7 100644
--- a/tools/bookmaker/definition.cpp
+++ b/tools/bookmaker/definition.cpp
@@ -530,6 +530,67 @@
             return paramError.reportError<bool>("#Param without param in #Method");
         }
     }
+    // check after end of #Line and before next child for description
+    const char* descStart = fContentStart;
+    const char* descEnd = nullptr;
+    for (auto& child : fChildren) {
+        if (MarkType::kAnchor == child->fMarkType) {
+            continue;
+        }
+        if (MarkType::kCode == child->fMarkType) {
+            continue;
+        }
+        if (MarkType::kDeprecated == child->fMarkType) {
+            return true;
+        }
+        if (MarkType::kExperimental == child->fMarkType) {
+            return true;
+        }
+        if (MarkType::kFormula == child->fMarkType) {
+            continue;
+        }
+        if (MarkType::kList == child->fMarkType) {
+            continue;
+        }
+        if (MarkType::kMarkChar == child->fMarkType) {
+            continue;
+        }
+        if (MarkType::kPhraseRef == child->fMarkType) {
+            continue;
+        }
+        if (MarkType::kPrivate == child->fMarkType) {
+            return true;
+        }
+        TextParser emptyCheck(fFileName, descStart, child->fStart, child->fLineCount);
+        if (!emptyCheck.eof() && emptyCheck.skipWhiteSpace()) {
+            descStart = emptyCheck.fChar;
+            emptyCheck.trimEnd();
+            descEnd = emptyCheck.fEnd;
+            break;
+        }
+        descStart = child->fTerminator;
+    }
+    if (!descEnd) {
+        return methodParser.reportError<bool>("missing description");
+    }
+    TextParser description(fFileName, descStart, descEnd, fLineCount);
+    // expect first word capitalized and pluralized. expect a trailing period
+    SkASSERT(descStart < descEnd);
+    if (!isupper(descStart[0])) {
+        description.reportWarning("expected capital");
+    } else if ('.' != descEnd[-1]) {
+        description.reportWarning("expected period");
+    } else {
+        if (!description.startsWith("For use by Android")) {
+            description.skipToSpace();
+            if (',' == description.fChar[-1]) {
+                --description.fChar;
+            }
+            if ('s' != description.fChar[-1]) {
+                description.reportWarning("expected plural");
+            }
+        }
+    }
     return true;
 }
 
@@ -852,6 +913,9 @@
     if (methodParser->skipExact("static")) {
         methodParser->skipWhiteSpace();
     }
+    if (methodParser->skipExact("virtual")) {
+        methodParser->skipWhiteSpace();
+    }
     const char* lastStart = methodParser->fChar;
     const char* nameInParser = methodParser->strnstr(name.c_str(), methodParser->fEnd);
     methodParser->skipTo(nameInParser);
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp
index 716d90a..238fcf5 100644
--- a/tools/bookmaker/includeParser.cpp
+++ b/tools/bookmaker/includeParser.cpp
@@ -2469,9 +2469,12 @@
                 // find token on start of line
                 auto lineIter = fParent->fTokens.end();
                 do {
+                    if (fParent->fTokens.begin() == lineIter) {
+                        break;
+                    }
                     --lineIter;
                 } while (lineIter->fContentStart > fLine);
-                if (lineIter->fContentStart < fLine) {
+                if (lineIter->fContentStart < fLine && fParent->fTokens.end() != lineIter) {
                     ++lineIter;
                 }
                 Definition* lineStart = &*lineIter;
@@ -2571,7 +2574,8 @@
                 list<Definition>::iterator baseIter = fParent->fTokens.end();
                 list<Definition>::iterator namedIter  = fParent->fTokens.end();
                 for (auto tokenIter = fParent->fTokens.end();
-                        fParent->fTokens.begin() != tokenIter--; ) {
+                        fParent->fTokens.begin() != tokenIter; ) {
+                    --tokenIter;
                     if (tokenIter->fLineCount == fLineCount) {
                         if ('f' == tokenIter->fStart[0] && isupper(tokenIter->fStart[1])) {
                             if (namedIter != fParent->fTokens.end()) {
diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp
index 18beff4..e16da32 100644
--- a/tools/bookmaker/includeWriter.cpp
+++ b/tools/bookmaker/includeWriter.cpp
@@ -2051,6 +2051,7 @@
             return this->reportError<bool>("expected fileName.h");
         }
         string skClassName = fileName.substr(0, fileName.length() - 2);
+        this->reset();
         fOut = fopen(fileName.c_str(), "wb");
         if (!fOut) {
             SkDebugf("could not open output file %s\n", fileName.c_str());
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp
index 653ca5e..4fdc206 100644
--- a/tools/bookmaker/mdOut.cpp
+++ b/tools/bookmaker/mdOut.cpp
@@ -1354,7 +1354,7 @@
                     if (fBmhParser.fTopicMap.end() == fBmhParser.fTopicMap.find(fullName)) {
                         (*subtopic)->reportError<void>("missing #Details subtopic");
                     }
-                    subtopicName = parentSubtopic->fName + '_' + subtopicName;
+             //       subtopicName = parentSubtopic->fName + '_' + subtopicName;
                     string noUnderscores = subtopicName;
                     replace_all(noUnderscores, "_", "&nbsp;");
                     details = this->anchorLocalRef(subtopicName, noUnderscores) + "&nbsp;";
diff --git a/tools/bookmaker/spellCheck.cpp b/tools/bookmaker/spellCheck.cpp
index a65e2c1..39621db 100644
--- a/tools/bookmaker/spellCheck.cpp
+++ b/tools/bookmaker/spellCheck.cpp
@@ -20,7 +20,10 @@
 when function crosses lines, whole thing isn't seen as a 'word' e.g., search for largeArc in path
 
 words in external not seen
+
+look for x-bit but allow x bits
  */
+
 struct CheckEntry {
     string fFile;
     int fLine;
@@ -389,6 +392,7 @@
     int inParens = 0;
     bool inQuotes = false;
     bool allLower = true;
+    char prePriorCh = 0;
     char priorCh = 0;
     char lastCh = 0;
     const char* wordStart = nullptr;
@@ -400,7 +404,10 @@
             if (!allLower || (!inQuotes && '\"' != lastCh && !inParens
                     && ')' != lastCh && !inAngles && '>' != lastCh)) {
                 string word(wordStart, (possibleEnd ? possibleEnd : wordEnd) - wordStart);
-                wordCheck(word);
+                if ("e" != word || !isdigit(prePriorCh) || ('+' != lastCh &&
+                        '-' != lastCh && !isdigit(lastCh))) {
+                    this->wordCheck(word);
+                }
             }
             wordStart = nullptr;
         }
@@ -469,6 +476,7 @@
                 wordEnd = chPtr;
                 break;
         }
+        prePriorCh = priorCh;
         priorCh = lastCh;
         lastCh = *chPtr;
     } while (++chPtr <= end);
@@ -655,6 +663,9 @@
         }
         iter->second.fCount += 1;
     } else {
+    if ("e" == str) {
+        SkDebugf("");
+    }
         CheckEntry* entry = &mappy[str];
         entry->fFile = fFileName;
         entry->fLine = fLineCount + fLocalLine;