diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java
index 199add94dfb9e7f8f181f9829a906648be66e1f3..f5a9377f6626145574f1ab7af31b6969a53aed8d 100644
--- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java
+++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java
@@ -88,7 +88,7 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
 
     @Override
     public void visit(ThematicBreak thematicBreak) {
-        writer.write("***");
+        writer.raw("***");
         writer.block();
     }
 
@@ -106,9 +106,9 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
                 if (heading.getLevel() == 1) {
                     // Note that it would be nice to match the length of the contents instead of just using 3, but that's
                     // not easy.
-                    writer.write("===");
+                    writer.raw("===");
                 } else {
-                    writer.write("---");
+                    writer.raw("---");
                 }
                 writer.block();
                 return;
@@ -117,9 +117,9 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
 
         // ATX headings: Can't have multiple lines, but up to level 6.
         for (int i = 0; i < heading.getLevel(); i++) {
-            writer.write('#');
+            writer.raw('#');
         }
-        writer.write(' ');
+        writer.raw(' ');
         visitChildren(heading);
 
         writer.block();
@@ -135,7 +135,7 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
         List<String> lines = getLines(literal);
         for (int i = 0; i < lines.size(); i++) {
             String line = lines.get(i);
-            writer.write(line);
+            writer.raw(line);
             if (i != lines.size() - 1) {
                 writer.line();
             }
@@ -156,19 +156,19 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
             writer.pushPrefix(indentPrefix);
         }
 
-        writer.write(fence);
+        writer.raw(fence);
         if (fencedCodeBlock.getInfo() != null) {
-            writer.write(fencedCodeBlock.getInfo());
+            writer.raw(fencedCodeBlock.getInfo());
         }
         writer.line();
         if (!literal.isEmpty()) {
             List<String> lines = getLines(literal);
             for (String line : lines) {
-                writer.write(line);
+                writer.raw(line);
                 writer.line();
             }
         }
-        writer.write(fence);
+        writer.raw(fence);
         if (indent > 0) {
             writer.popPrefix();
         }
@@ -180,7 +180,7 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
         List<String> lines = getLines(htmlBlock.getLiteral());
         for (int i = 0; i < lines.size(); i++) {
             String line = lines.get(i);
-            writer.write(line);
+            writer.raw(line);
             if (i != lines.size() - 1) {
                 writer.line();
             }
@@ -262,7 +262,7 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
         // If the literal includes backticks, we can surround them by using one more backtick.
         int backticks = findMaxRunLength('`', literal);
         for (int i = 0; i < backticks + 1; i++) {
-            writer.write('`');
+            writer.raw('`');
         }
         // If the literal starts or ends with a backtick, surround it with a single space.
         // If it starts and ends with a space (but is not only spaces), add an additional space (otherwise they would
@@ -270,14 +270,14 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
         boolean addSpace = literal.startsWith("`") || literal.endsWith("`") ||
                 (literal.startsWith(" ") && literal.endsWith(" ") && Parsing.hasNonSpace(literal));
         if (addSpace) {
-            writer.write(' ');
+            writer.raw(' ');
         }
-        writer.write(literal);
+        writer.raw(literal);
         if (addSpace) {
-            writer.write(' ');
+            writer.raw(' ');
         }
         for (int i = 0; i < backticks + 1; i++) {
-            writer.write('`');
+            writer.raw('`');
         }
     }
 
@@ -285,16 +285,16 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
     public void visit(Emphasis emphasis) {
         // When emphasis is nested, a different delimiter needs to be used
         char delimiter = writer.getLastChar() == '*' ? '_' : '*';
-        writer.write(delimiter);
+        writer.raw(delimiter);
         super.visit(emphasis);
-        writer.write(delimiter);
+        writer.raw(delimiter);
     }
 
     @Override
     public void visit(StrongEmphasis strongEmphasis) {
-        writer.write("**");
+        writer.raw("**");
         super.visit(strongEmphasis);
-        writer.write("**");
+        writer.raw("**");
     }
 
     @Override
@@ -309,12 +309,12 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
 
     @Override
     public void visit(HtmlInline htmlInline) {
-        writer.write(htmlInline.getLiteral());
+        writer.raw(htmlInline.getLiteral());
     }
 
     @Override
     public void visit(HardLineBreak hardLineBreak) {
-        writer.write("  ");
+        writer.raw("  ");
         writer.line();
     }
 
@@ -339,19 +339,19 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
             switch (c) {
                 case '-': {
                     // Would be ambiguous with a bullet list marker, escape
-                    writer.write("\\-");
+                    writer.raw("\\-");
                     literal = literal.substring(1);
                     break;
                 }
                 case '#': {
                     // Would be ambiguous with an ATX heading, escape
-                    writer.write("\\#");
+                    writer.raw("\\#");
                     literal = literal.substring(1);
                     break;
                 }
                 case '=': {
                     // Would be ambiguous with a Setext heading, escape
-                    writer.write("\\=");
+                    writer.raw("\\=");
                     literal = literal.substring(1);
                     break;
                 }
@@ -368,19 +368,19 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
                     // Check for ordered list marker
                     Matcher m = orderedListMarkerPattern.matcher(literal);
                     if (m.find()) {
-                        writer.write(m.group(1));
-                        writer.write("\\" + m.group(2));
+                        writer.raw(m.group(1));
+                        writer.raw("\\" + m.group(2));
                         literal = literal.substring(m.end());
                     }
                     break;
                 }
                 case '\t': {
-                    writer.write("&#9;");
+                    writer.raw("&#9;");
                     literal = literal.substring(1);
                     break;
                 }
                 case ' ': {
-                    writer.write("&#32;");
+                    writer.raw("&#32;");
                     literal = literal.substring(1);
                     break;
                 }
@@ -391,10 +391,10 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
 
         if (literal.endsWith("!") && text.getNext() instanceof Link) {
             // If we wrote the `!` unescaped, it would turn the link into an image instead.
-            writer.writeEscaped(literal.substring(0, literal.length() - 1), escape);
-            writer.write("\\!");
+            writer.text(literal.substring(0, literal.length() - 1), escape);
+            writer.raw("\\!");
         } else {
-            writer.writeEscaped(literal, escape);
+            writer.text(literal, escape);
         }
     }
 
@@ -455,24 +455,24 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
     }
 
     private void writeLinkLike(String title, String destination, Node node, String opener) {
-        writer.write(opener);
+        writer.raw(opener);
         visitChildren(node);
-        writer.write(']');
-        writer.write('(');
+        writer.raw(']');
+        writer.raw('(');
         if (contains(destination, linkDestinationNeedsAngleBrackets)) {
-            writer.write('<');
-            writer.writeEscaped(destination, linkDestinationEscapeInAngleBrackets);
-            writer.write('>');
+            writer.raw('<');
+            writer.text(destination, linkDestinationEscapeInAngleBrackets);
+            writer.raw('>');
         } else {
-            writer.write(destination);
+            writer.raw(destination);
         }
         if (title != null) {
-            writer.write(' ');
-            writer.write('"');
-            writer.writeEscaped(title, linkTitleEscapeInQuotes);
-            writer.write('"');
+            writer.raw(' ');
+            writer.raw('"');
+            writer.text(title, linkTitleEscapeInQuotes);
+            writer.raw('"');
         }
-        writer.write(')');
+        writer.raw(')');
     }
 
     private static class ListHolder {
diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java
index ef294535933051fbd8aa43641b4e86f1a264f7cc..3b2ae18b02ab6a3830bfd69e156dd269cc06242e 100644
--- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java
+++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java
@@ -5,6 +5,9 @@ import org.commonmark.internal.util.CharMatcher;
 import java.io.IOException;
 import java.util.LinkedList;
 
+/**
+ * Writer for Markdown (CommonMark) text.
+ */
 public class MarkdownWriter {
 
     private final Appendable buffer;
@@ -19,28 +22,29 @@ public class MarkdownWriter {
         buffer = out;
     }
 
-    public char getLastChar() {
-        return lastChar;
-    }
-
     /**
-     * @return whether we're at the line start (not counting any prefixes), i.e. after a {@link #line} or {@link #block}.
+     * Write the supplied string (raw/unescaped).
      */
-    public boolean isAtLineStart() {
-        return atLineStart;
-    }
-
-    public void write(String s) {
+    public void raw(String s) {
         flushBlockSeparator();
         append(s);
     }
 
-    public void write(char c) {
+    /**
+     * Write the supplied character (raw/unescaped).
+     */
+    public void raw(char c) {
         flushBlockSeparator();
         append(c);
     }
 
-    public void writeEscaped(String s, CharMatcher escape) {
+    /**
+     * Write the supplied string with escaping.
+     *
+     * @param s      the string to write
+     * @param escape which characters to escape
+     */
+    public void text(String s, CharMatcher escape) {
         if (s.isEmpty()) {
             return;
         }
@@ -66,6 +70,9 @@ public class MarkdownWriter {
         atLineStart = false;
     }
 
+    /**
+     * Write a newline (line terminator).
+     */
     public void line() {
         append('\n');
         writePrefixes();
@@ -83,20 +90,67 @@ public class MarkdownWriter {
         atLineStart = true;
     }
 
+    /**
+     * Push a prefix onto the top of the stack. All prefixes are written at the beginning of each line, until the
+     * prefix is popped again.
+     *
+     * @param prefix the raw prefix string
+     */
     public void pushPrefix(String prefix) {
         prefixes.addLast(prefix);
     }
 
+    /**
+     * Write a prefix.
+     *
+     * @param prefix the raw prefix string to write
+     */
     public void writePrefix(String prefix) {
         boolean tmp = atLineStart;
-        write(prefix);
+        raw(prefix);
         atLineStart = tmp;
     }
 
+    /**
+     * Remove the last prefix from the top of the stack.
+     */
     public void popPrefix() {
         prefixes.removeLast();
     }
 
+    /**
+     * @return the last character that was written
+     */
+    public char getLastChar() {
+        return lastChar;
+    }
+
+    /**
+     * @return whether we're at the line start (not counting any prefixes), i.e. after a {@link #line} or {@link #block}.
+     */
+    public boolean isAtLineStart() {
+        return atLineStart;
+    }
+
+    /**
+     * @return whether blocks are currently set to tight or loose, see {@link #setTight(boolean)}
+     */
+    public boolean getTight() {
+        return tight;
+    }
+
+    /**
+     * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight
+     * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines
+     * within the list.
+     * <p>
+     * Note that changing this does not affect block separators that have already been enqueued (with {@link #block()},
+     * only future ones.
+     */
+    public void setTight(boolean tight) {
+        this.tight = tight;
+    }
+
     private void append(String s) {
         try {
             buffer.append(s);
@@ -144,23 +198,4 @@ public class MarkdownWriter {
             blockSeparator = 0;
         }
     }
-
-    /**
-     * @return whether blocks are currently set to tight or loose, see {@link #setTight(boolean)}
-     */
-    public boolean getTight() {
-        return tight;
-    }
-
-    /**
-     * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight
-     * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines
-     * within the list.
-     * <p>
-     * Note that changing this does not affect block separators that have already been enqueued (with {@link #block()},
-     * only future ones.
-     */
-    public void setTight(boolean tight) {
-        this.tight = tight;
-    }
 }