From eef6e132080c5e46ba8c47ecfaca83fa8e0e214e Mon Sep 17 00:00:00 2001 From: Benjamin Culkin Date: Wed, 28 Jan 2026 21:36:12 -0500 Subject: Add various text UI components This adds a variety of text UI components, namely two suites: * One that is geared towards JSON * One that is geared towards Markdown Details to (perhaps) follow later --- .../main/java/bjc/utils/gui/MarkdownEditorKit.java | 948 +++++++++++++++++++++ 1 file changed, 948 insertions(+) create mode 100644 base/src/main/java/bjc/utils/gui/MarkdownEditorKit.java (limited to 'base/src/main/java/bjc/utils/gui/MarkdownEditorKit.java') diff --git a/base/src/main/java/bjc/utils/gui/MarkdownEditorKit.java b/base/src/main/java/bjc/utils/gui/MarkdownEditorKit.java new file mode 100644 index 0000000..24965e8 --- /dev/null +++ b/base/src/main/java/bjc/utils/gui/MarkdownEditorKit.java @@ -0,0 +1,948 @@ +package bjc.utils.gui; + +import javax.swing.*; +import javax.swing.text.*; + +import java.awt.*; +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Basic Markdown -> StyledDocument renderer for Swing. + * + * Supported: + * - Headings #..### + * - Bold **...** / __...__ + * - Italic *...* / _..._ + * - Inline code `...` + * - Fenced code blocks ``` or ~~~ + * - Blockquotes > + * - Lists (unordered and ordered) + * - Links [text](url) (href stored in attribute) + * - Tables (pipe tables) with inline Markdown inside cells + * + * Usage: + * JTextPane pane = new JTextPane(); + * pane.setEditorKit(new MarkdownEditorKit()); + * ((MarkdownDocument)pane.getDocument()).setMarkdown(markdownString); + */ +public class MarkdownEditorKit extends StyledEditorKit { + private static final long serialVersionUID = 350927457022623776L; + + @Override + public String getContentType() { + return "text/markdown"; + } + + @Override + public Document createDefaultDocument() { + return new MarkdownDocument(); + } + + /** + * Set the Markdown contents of a JTextPane + * + * @param pane The JTextPane to set the contents of + * @param markdown The Markdown to set as the contents + */ + public void setMarkdown(JTextPane pane, String markdown) { + if (!(pane.getDocument() instanceof MarkdownDocument)) { + pane.setDocument(createDefaultDocument()); + } + ((MarkdownDocument) pane.getDocument()).setMarkdown(markdown); + } + + @Override + public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException { + String markdown = readAll(in); + if (doc instanceof StyledDocument) { + MarkdownRenderer.render((StyledDocument) doc, markdown, pos, false); + } else { + super.read(new StringReader(markdown), doc, pos); + } + } + + @Override + public void write(Writer out, Document doc, int pos, int len) throws IOException, BadLocationException { + // Outputs rendered text, not original Markdown. + out.write(doc.getText(pos, len)); + } + + private static String readAll(Reader r) throws IOException { + StringBuilder sb = new StringBuilder(4096); + char[] buf = new char[4096]; + int n; + while ((n = r.read(buf)) >= 0) + sb.append(buf, 0, n); + return sb.toString(); + } + + // ------------------------------------------------------------------------- + // Document + // ------------------------------------------------------------------------- + + /** + * A instance of {@link StyledDocument} for Markdown + */ + public static class MarkdownDocument extends DefaultStyledDocument { + private static final long serialVersionUID = 3602531682908814242L; + + /** + * The name for the source property + */ + public static final String PROP_SOURCE_MARKDOWN = "markdown.source"; + /** + * The name for the Link attribute + */ + public static final String ATTR_LINK_HREF = "markdown.link.href"; + + // Base / inline + final SimpleAttributeSet base = new SimpleAttributeSet(); + final SimpleAttributeSet bold = new SimpleAttributeSet(); + final SimpleAttributeSet italic = new SimpleAttributeSet(); + final SimpleAttributeSet code = new SimpleAttributeSet(); + final SimpleAttributeSet link = new SimpleAttributeSet(); + + // Headings + final SimpleAttributeSet h1 = new SimpleAttributeSet(); + final SimpleAttributeSet h2 = new SimpleAttributeSet(); + final SimpleAttributeSet h3 = new SimpleAttributeSet(); + + // Blockquote + final SimpleAttributeSet blockquoteText = new SimpleAttributeSet(); + final SimpleAttributeSet blockquotePara = new SimpleAttributeSet(); + + // List + final SimpleAttributeSet listPara = new SimpleAttributeSet(); + + // Code block + final SimpleAttributeSet codeBlockText = new SimpleAttributeSet(); + final SimpleAttributeSet codeBlockPara = new SimpleAttributeSet(); + + // Table + final SimpleAttributeSet tableText = new SimpleAttributeSet(); + final SimpleAttributeSet tableHeaderText = new SimpleAttributeSet(); + final SimpleAttributeSet tablePara = new SimpleAttributeSet(); + + /** + * Create a new Markdown document + */ + public MarkdownDocument() { + super(); + + StyleConstants.setFontSize(base, 13); + + StyleConstants.setBold(bold, true); + StyleConstants.setItalic(italic, true); + + StyleConstants.setFontFamily(code, "Monospaced"); + StyleConstants.setBackground(code, new Color(245, 245, 245)); + + StyleConstants.setForeground(link, new Color(0, 102, 204)); + StyleConstants.setUnderline(link, true); + + StyleConstants.setBold(h1, true); + StyleConstants.setFontSize(h1, 22); + + StyleConstants.setBold(h2, true); + StyleConstants.setFontSize(h2, 18); + + StyleConstants.setBold(h3, true); + StyleConstants.setFontSize(h3, 15); + + StyleConstants.setForeground(blockquoteText, new Color(90, 90, 90)); + StyleConstants.setItalic(blockquoteText, true); + + StyleConstants.setLeftIndent(blockquotePara, 18f); + StyleConstants.setSpaceAbove(blockquotePara, 3f); + StyleConstants.setSpaceBelow(blockquotePara, 3f); + + StyleConstants.setLeftIndent(listPara, 18f); + StyleConstants.setFirstLineIndent(listPara, -12f); + + StyleConstants.setFontFamily(codeBlockText, "Monospaced"); + StyleConstants.setBackground(codeBlockText, new Color(245, 245, 245)); + + StyleConstants.setLeftIndent(codeBlockPara, 18f); + StyleConstants.setSpaceAbove(codeBlockPara, 6f); + StyleConstants.setSpaceBelow(codeBlockPara, 6f); + + // Tables: render like a code-ish block + StyleConstants.setFontFamily(tableText, "Monospaced"); + StyleConstants.setBackground(tableText, new Color(245, 245, 245)); + + tableHeaderText.addAttributes(tableText); + StyleConstants.setBold(tableHeaderText, true); + + StyleConstants.setLeftIndent(tablePara, 18f); + StyleConstants.setSpaceAbove(tablePara, 6f); + StyleConstants.setSpaceBelow(tablePara, 6f); + } + + /** + * Set the contents of this document + * + * @param markdown The markdown to render + */ + public void setMarkdown(String markdown) { + putProperty(PROP_SOURCE_MARKDOWN, markdown); + try { + MarkdownRenderer.render(this, markdown, 0, true); + } catch (BadLocationException e) { + throw new RuntimeException(e); + } + } + } + + // ------------------------------------------------------------------------- + // Renderer + // ------------------------------------------------------------------------- + + static final class MarkdownRenderer { + + static void render(StyledDocument doc, String markdown, int argInsertPos, boolean clear) + throws BadLocationException { + + int insertPos = argInsertPos; + if (clear) { + doc.remove(0, doc.getLength()); + insertPos = 0; + } + + Attr a = Attr.from(doc); + + String[] lines = markdown.replace("\r\n", "\n").replace('\r', '\n').split("\n", -1); + + int offset = insertPos; + boolean inFence = false; + String fenceDelim = null; + + for (int li = 0; li < lines.length; li++) { + String line = lines[li]; + + // Fenced code blocks + String trimmed = line.trim(); + if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) { + String delim = trimmed.substring(0, 3); + if (!inFence) { + inFence = true; + fenceDelim = delim; + } else if (delim.equals(fenceDelim)) { + inFence = false; + fenceDelim = null; + if (li != lines.length - 1) { + offset = insert(doc, offset, "\n", a.base); + } + } + continue; + } + + if (inFence) { + int start = offset; + offset = insert(doc, offset, line, a.codeBlockText); + offset = insert(doc, offset, "\n", a.codeBlockText); + applyParagraph(doc, start, offset - start, a.codeBlockPara); + continue; + } + + // Blank line + if (isBlank(line)) { + offset = insert(doc, offset, "\n", a.base); + continue; + } + + // Tables: detect header + divider + if (li + 1 < lines.length) { + TableHeader th = TableHeader.tryParse(line, lines[li + 1]); + if (th != null) { + // Collect rows + List> rows = new ArrayList<>(); + rows.add(th.headerCells); + + int r = li + 2; + while (r < lines.length && !isBlank(lines[r])) { + List cells = splitPipeRow(lines[r]); + if (cells == null || cells.size() != th.colCount) + break; + rows.add(cells); + r++; + } + + int start = offset; + offset = renderTable(doc, offset, rows, a); + applyParagraph(doc, start, offset - start, a.tablePara); + + li = r - 1; + continue; + } + } + + // Headings #, ##, ### + Heading h = Heading.parse(line); + if (h != null) { + AttributeSet hStyle; + if (h.level == 1) + hStyle = a.h1; + else if (h.level == 2) + hStyle = a.h2; + else + hStyle = a.h3; + + int start = offset; + offset = renderInline(doc, offset, h.text, a, hStyle); + offset = insert(doc, offset, "\n", a.base); + + SimpleAttributeSet para = new SimpleAttributeSet(); + StyleConstants.setSpaceAbove(para, 6f); + StyleConstants.setSpaceBelow(para, 4f); + applyParagraph(doc, start, offset - start, para); + continue; + } + + // Blockquote + if (line.startsWith(">")) { + String q = line; + while (q.startsWith(">")) + q = q.substring(1); + if (q.startsWith(" ")) + q = q.substring(1); + + int start = offset; + offset = insert(doc, offset, "▌ ", a.blockquoteText); + offset = renderInline(doc, offset, q, a, a.blockquoteText); + offset = insert(doc, offset, "\n", a.base); + applyParagraph(doc, start, offset - start, a.blockquotePara); + continue; + } + + // Lists + ListItem item = ListItem.parse(line); + if (item != null) { + int start = offset; + offset = insert(doc, offset, item.prefix, a.base); + offset = renderInline(doc, offset, item.text, a, a.base); + offset = insert(doc, offset, "\n", a.base); + applyParagraph(doc, start, offset - start, a.listPara); + continue; + } + + // Normal line = paragraph + int start = offset; + offset = renderInline(doc, offset, line, a, a.base); + offset = insert(doc, offset, "\n", a.base); + + SimpleAttributeSet para = new SimpleAttributeSet(); + StyleConstants.setSpaceBelow(para, 2f); + applyParagraph(doc, start, offset - start, para); + } + } + + // --- table rendering with inline markdown inside cells --- + + private static int renderTable(StyledDocument doc, int argOffset, List> rows, Attr a) + throws BadLocationException { + + int offset = argOffset; + int cols = rows.get(0).size(); + int[] widths = new int[cols]; + + // compute widths based on visible (rendered) length + for (List row : rows) { + for (int c = 0; c < cols; c++) { + String cell = safeCell(row, c); + int v = visibleLen(cell); + if (v > widths[c]) + widths[c] = v; + } + } + for (int c = 0; c < cols; c++) { + if (widths[c] < 3) + widths[c] = 3; + } + + // header row + offset = renderTableRow(doc, offset, rows.get(0), widths, a, a.tableHeaderText); + + // separator row (plain dashes) + offset = renderTableSeparator(doc, offset, widths, a); + + // body rows + for (int r = 1; r < rows.size(); r++) { + offset = renderTableRow(doc, offset, rows.get(r), widths, a, a.tableText); + } + + return offset; + } + + private static int renderTableRow(StyledDocument doc, int argOffset, List cells, int[] widths, Attr a, + AttributeSet rowStyle) throws BadLocationException { + + int offset = argOffset; + offset = insert(doc, offset, "|", rowStyle); + + for (int c = 0; c < widths.length; c++) { + String cell = safeCell(cells, c); + + offset = insert(doc, offset, " ", rowStyle); + + // Render inline markdown inside the cell using the table rowStyle as the base. + offset = renderInline(doc, offset, cell, a, rowStyle); + + int pad = widths[c] - visibleLen(cell); + if (pad > 0) { + offset = insert(doc, offset, repeat(' ', pad), rowStyle); + } + + offset = insert(doc, offset, " |", rowStyle); + } + + offset = insert(doc, offset, "\n", rowStyle); + return offset; + } + + private static int renderTableSeparator(StyledDocument doc, int argOffset, int[] widths, Attr a) + throws BadLocationException { + int offset = argOffset; + + AttributeSet s = a.tableText; + + offset = insert(doc, offset, "|", s); + for (int c = 0; c < widths.length; c++) { + offset = insert(doc, offset, " ", s); + offset = insert(doc, offset, repeat('-', widths[c]), s); + offset = insert(doc, offset, " |", s); + } + offset = insert(doc, offset, "\n", s); + return offset; + } + + private static String safeCell(List row, int idx) { + if (row == null) + return ""; + if (idx < 0 || idx >= row.size()) + return ""; + return row.get(idx); + } + + /** + * Splits a pipe table row into cells. - Allows optional leading/trailing pipes. + * - Does NOT split on escaped pipes (\|). - Does NOT split on pipes inside + * inline code spans (`...`). + */ + private static List splitPipeRow(String line) { + if (line == null) + return null; + if (line.indexOf('|') < 0) + return null; + + String s = line.trim(); + + if (s.startsWith("|")) + s = s.substring(1); + if (s.endsWith("|")) + s = s.substring(0, s.length() - 1); + + List cells = new ArrayList<>(); + StringBuilder cell = new StringBuilder(); + + boolean escaped = false; + boolean inCode = false; + + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + + if (escaped) { + cell.append(ch); + escaped = false; + continue; + } + + if (ch == '\\') { + escaped = true; + continue; + } + + if (ch == '`') { + inCode = !inCode; + cell.append(ch); // keep backticks so inline parsing still sees them + continue; + } + + if (ch == '|' && !inCode) { + cells.add(cell.toString().trim()); + cell.setLength(0); + continue; + } + + cell.append(ch); + } + + if (escaped) + cell.append('\\'); + cells.add(cell.toString().trim()); + + return cells; + } + + // --- visible length for table width computation (basic markdown stripping) --- + + private static int visibleLen(String text) { + if (text == null || text.isEmpty()) + return 0; + + int i = 0; + int len = 0; + + while (i < text.length()) { + char c = text.charAt(i); + + // escape: \x counts as x + if (c == '\\' && i + 1 < text.length()) { + len += 1; + i += 2; + continue; + } + + // code span: `...` + if (c == '`') { + int end = findNext(text, i + 1, '`'); + if (end > i + 1) { + len += (end - (i + 1)); + i = end + 1; + continue; + } + } + + // link: [label](url) => count label + if (c == '[') { + int closeBracket = text.indexOf(']', i + 1); + if (closeBracket > i + 1 && closeBracket + 1 < text.length() + && text.charAt(closeBracket + 1) == '(') { + int closeParen = text.indexOf(')', closeBracket + 2); + if (closeParen > closeBracket + 2) { + String label = text.substring(i + 1, closeBracket); + len += visibleLen(label); + i = closeParen + 1; + continue; + } + } + } + + // bold: **...** or __...__ + if (startsWith(text, i, "**") || startsWith(text, i, "__")) { + String delim = text.substring(i, i + 2); + int end = text.indexOf(delim, i + 2); + if (end > i + 2) { + String inner = text.substring(i + 2, end); + len += visibleLen(inner); + i = end + 2; + continue; + } + } + + // italic: *...* or _..._ + if (c == '*' || c == '_') { + int end = text.indexOf(c, i + 1); + if (end > i + 1) { + String inner = text.substring(i + 1, end); + if (!isBlank(inner)) { + len += visibleLen(inner); + i = end + 1; + continue; + } + } + } + + len += 1; + i += 1; + } + + return len; + } + + private static int findNext(String s, int start, char needle) { + // Finds next unescaped needle. + boolean escaped = false; + for (int i = start; i < s.length(); i++) { + char c = s.charAt(i); + if (escaped) { + escaped = false; + continue; + } + if (c == '\\') { + escaped = true; + continue; + } + if (c == needle) + return i; + } + return -1; + } + + // --- inline rendering --- + + private static int insert(StyledDocument doc, int offset, String s, AttributeSet attrs) + throws BadLocationException { + doc.insertString(offset, s, attrs); + return offset + s.length(); + } + + private static void applyParagraph(StyledDocument doc, int start, int length, AttributeSet paraAttrs) { + doc.setParagraphAttributes(start, Math.max(1, length), paraAttrs, false); + } + + /** + * Render inline Markdown inside a single line. Priority: - code spans `...` - + * links [text](url) - bold **...** or __...__ - italic *...* or _..._ + */ + private static int renderInline(StyledDocument doc, int argOffset, String text, Attr a, AttributeSet baseStyle) + throws BadLocationException { + int offset = argOffset; + + if (text == null || text.isEmpty()) + return offset; + + int i = 0; + StringBuilder plain = new StringBuilder(); + + while (i < text.length()) { + char c = text.charAt(i); + + // Escape + if (c == '\\' && i + 1 < text.length()) { + plain.append(text.charAt(i + 1)); + i += 2; + continue; + } + + // Code span + if (c == '`') { + int end = text.indexOf('`', i + 1); + if (end > i + 1) { + offset = flushPlain(doc, offset, plain, baseStyle); + String code = text.substring(i + 1, end); + offset = insert(doc, offset, code, merge(baseStyle, a.code)); + i = end + 1; + continue; + } + } + + // Link [text](url) + if (c == '[') { + int closeBracket = text.indexOf(']', i + 1); + if (closeBracket > i + 1 && closeBracket + 1 < text.length() + && text.charAt(closeBracket + 1) == '(') { + int closeParen = text.indexOf(')', closeBracket + 2); + if (closeParen > closeBracket + 2) { + String label = text.substring(i + 1, closeBracket); + String href = text.substring(closeBracket + 2, closeParen); + + offset = flushPlain(doc, offset, plain, baseStyle); + + SimpleAttributeSet linkAttrs = new SimpleAttributeSet(); + linkAttrs.addAttributes(baseStyle); + linkAttrs.addAttributes(a.link); + linkAttrs.addAttribute(MarkdownDocument.ATTR_LINK_HREF, href); + + offset = insert(doc, offset, label, linkAttrs); + i = closeParen + 1; + continue; + } + } + } + + // Bold **...** or __...__ + if (startsWith(text, i, "**") || startsWith(text, i, "__")) { + String delim = text.substring(i, i + 2); + int end = text.indexOf(delim, i + 2); + if (end > i + 2) { + offset = flushPlain(doc, offset, plain, baseStyle); + String content = text.substring(i + 2, end); + offset = insert(doc, offset, content, merge(baseStyle, a.bold)); + i = end + 2; + continue; + } + } + + // Italic *...* or _..._ + if (c == '*' || c == '_') { + int end = text.indexOf(c, i + 1); + if (end > i + 1) { + String content = text.substring(i + 1, end); + if (!isBlank(content)) { + offset = flushPlain(doc, offset, plain, baseStyle); + offset = insert(doc, offset, content, merge(baseStyle, a.italic)); + i = end + 1; + continue; + } + } + } + + // Default: plain + plain.append(c); + i++; + } + + return flushPlain(doc, offset, plain, baseStyle); + } + + private static int flushPlain(StyledDocument doc, int argOffset, StringBuilder plain, AttributeSet baseStyle) + throws BadLocationException { + int offset = argOffset; + + if (plain.length() == 0) + return offset; + offset = insert(doc, offset, plain.toString(), baseStyle); + plain.setLength(0); + return offset; + } + + private static boolean startsWith(String s, int i, String prefix) { + return i + prefix.length() <= s.length() && s.regionMatches(i, prefix, 0, prefix.length()); + } + + private static AttributeSet merge(AttributeSet base, AttributeSet extra) { + SimpleAttributeSet merged = new SimpleAttributeSet(); + merged.addAttributes(base); + merged.addAttributes(extra); + return merged; + } + + // --- line parsers / helpers --- + + private static boolean isBlank(String s) { + if (s == null || s.isEmpty()) + return true; + for (int i = 0; i < s.length(); i++) { + if (!Character.isWhitespace(s.charAt(i))) + return false; + } + return true; + } + + private static String repeat(char ch, int count) { + if (count <= 0) + return ""; + StringBuilder sb = new StringBuilder(count); + for (int i = 0; i < count; i++) + sb.append(ch); + return sb.toString(); + } + + private static String stripLeading(String s) { + if (s == null) + return ""; + int i = 0; + while (i < s.length() && Character.isWhitespace(s.charAt(i))) + i++; + return s.substring(i); + } + + private static final class Heading { + final int level; + final String text; + + Heading(int level, String text) { + this.level = level; + this.text = text; + } + + static Heading parse(String line) { + if (line == null || line.isEmpty()) + return null; + int i = 0; + while (i < line.length() && line.charAt(i) == '#') + i++; + if (i == 0 || i > 6) + return null; + if (i < line.length() && line.charAt(i) == ' ') { + String t = line.substring(i + 1); + int level = i; + if (level > 3) + level = 3; // cap for styling + return new Heading(level, t); + } + return null; + } + } + + private static final class ListItem { + final String prefix; + final String text; + + ListItem(String prefix, String text) { + this.prefix = prefix; + this.text = text; + } + + static ListItem parse(String line) { + String s = stripLeading(line); + + if (s.startsWith("- ") || s.startsWith("* ") || s.startsWith("+ ")) { + return new ListItem("• ", s.substring(2)); + } + + int n = 0; + while (n < s.length() && Character.isDigit(s.charAt(n))) + n++; + if (n > 0 && n + 1 < s.length()) { + char sep = s.charAt(n); + if ((sep == '.' || sep == ')') && s.charAt(n + 1) == ' ') { + String num = s.substring(0, n); + return new ListItem(num + sep + " ", s.substring(n + 2)); + } + } + + return null; + } + } + + private static final class TableHeader { + final List headerCells; + final int colCount; + + TableHeader(List headerCells, int colCount) { + this.headerCells = headerCells; + this.colCount = colCount; + } + + static TableHeader tryParse(String headerLine, String dividerLine) { + List header = splitPipeRow(headerLine); + if (header == null || header.isEmpty()) + return null; + + List div = splitPipeRow(dividerLine); + if (div == null || div.size() != header.size()) + return null; + + for (int i = 0; i < div.size(); i++) { + String d = div.get(i).replace(" ", ""); + // allow :---:, ---:, :---, --- + if (!d.matches(":?-{3,}:?")) + return null; + } + + return new TableHeader(header, header.size()); + } + } + + private static final class Attr { + final SimpleAttributeSet base; + final SimpleAttributeSet bold; + final SimpleAttributeSet italic; + final SimpleAttributeSet code; + final SimpleAttributeSet link; + + final SimpleAttributeSet h1; + final SimpleAttributeSet h2; + final SimpleAttributeSet h3; + + final SimpleAttributeSet blockquoteText; + final SimpleAttributeSet blockquotePara; + + final SimpleAttributeSet listPara; + + final SimpleAttributeSet codeBlockText; + final SimpleAttributeSet codeBlockPara; + + final SimpleAttributeSet tableText; + final SimpleAttributeSet tableHeaderText; + final SimpleAttributeSet tablePara; + + Attr(SimpleAttributeSet base, SimpleAttributeSet bold, SimpleAttributeSet italic, SimpleAttributeSet code, + SimpleAttributeSet link, SimpleAttributeSet h1, SimpleAttributeSet h2, SimpleAttributeSet h3, + SimpleAttributeSet blockquoteText, SimpleAttributeSet blockquotePara, SimpleAttributeSet listPara, + SimpleAttributeSet codeBlockText, SimpleAttributeSet codeBlockPara, SimpleAttributeSet tableText, + SimpleAttributeSet tableHeaderText, SimpleAttributeSet tablePara) { + this.base = base; + this.bold = bold; + this.italic = italic; + this.code = code; + this.link = link; + this.h1 = h1; + this.h2 = h2; + this.h3 = h3; + this.blockquoteText = blockquoteText; + this.blockquotePara = blockquotePara; + this.listPara = listPara; + this.codeBlockText = codeBlockText; + this.codeBlockPara = codeBlockPara; + this.tableText = tableText; + this.tableHeaderText = tableHeaderText; + this.tablePara = tablePara; + } + + static Attr from(StyledDocument doc) { + if (doc instanceof MarkdownDocument) { + MarkdownDocument md = (MarkdownDocument) doc; + return new Attr(md.base, md.bold, md.italic, md.code, md.link, md.h1, md.h2, md.h3, + md.blockquoteText, md.blockquotePara, md.listPara, md.codeBlockText, md.codeBlockPara, + md.tableText, md.tableHeaderText, md.tablePara); + } + + // fallback defaults + SimpleAttributeSet base = new SimpleAttributeSet(); + StyleConstants.setFontSize(base, 13); + + SimpleAttributeSet bold = new SimpleAttributeSet(); + StyleConstants.setBold(bold, true); + + SimpleAttributeSet italic = new SimpleAttributeSet(); + StyleConstants.setItalic(italic, true); + + SimpleAttributeSet code = new SimpleAttributeSet(); + StyleConstants.setFontFamily(code, "Monospaced"); + StyleConstants.setBackground(code, new Color(245, 245, 245)); + + SimpleAttributeSet link = new SimpleAttributeSet(); + StyleConstants.setForeground(link, new Color(0, 102, 204)); + StyleConstants.setUnderline(link, true); + + SimpleAttributeSet h1 = new SimpleAttributeSet(); + StyleConstants.setBold(h1, true); + StyleConstants.setFontSize(h1, 22); + + SimpleAttributeSet h2 = new SimpleAttributeSet(); + StyleConstants.setBold(h2, true); + StyleConstants.setFontSize(h2, 18); + + SimpleAttributeSet h3 = new SimpleAttributeSet(); + StyleConstants.setBold(h3, true); + StyleConstants.setFontSize(h3, 15); + + SimpleAttributeSet bqt = new SimpleAttributeSet(); + StyleConstants.setForeground(bqt, new Color(90, 90, 90)); + StyleConstants.setItalic(bqt, true); + + SimpleAttributeSet bqPara = new SimpleAttributeSet(); + StyleConstants.setLeftIndent(bqPara, 18f); + + SimpleAttributeSet listPara = new SimpleAttributeSet(); + StyleConstants.setLeftIndent(listPara, 18f); + StyleConstants.setFirstLineIndent(listPara, -12f); + + SimpleAttributeSet cbText = new SimpleAttributeSet(); + StyleConstants.setFontFamily(cbText, "Monospaced"); + StyleConstants.setBackground(cbText, new Color(245, 245, 245)); + + SimpleAttributeSet cbPara = new SimpleAttributeSet(); + StyleConstants.setLeftIndent(cbPara, 18f); + + SimpleAttributeSet tableText = new SimpleAttributeSet(); + StyleConstants.setFontFamily(tableText, "Monospaced"); + StyleConstants.setBackground(tableText, new Color(245, 245, 245)); + + SimpleAttributeSet tableHeader = new SimpleAttributeSet(); + tableHeader.addAttributes(tableText); + StyleConstants.setBold(tableHeader, true); + + SimpleAttributeSet tablePara = new SimpleAttributeSet(); + StyleConstants.setLeftIndent(tablePara, 18f); + + return new Attr(base, bold, italic, code, link, h1, h2, h3, bqt, bqPara, listPara, cbText, cbPara, + tableText, tableHeader, tablePara); + } + } + } +} \ No newline at end of file -- cgit v1.2.3