CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
MarkdownParser.cpp
Go to the documentation of this file.
2
3namespace cdc::ui {
4namespace {
5
6struct Span {
7 const char* s;
8 uint16_t len;
9};
10
11bool isSpace(char c) { return c == ' ' || c == '\t'; }
12
15size_t nextLine(const char* src, size_t n, size_t pos, Span& out) {
16 size_t start = pos;
17 size_t i = pos;
18 while (i < n && src[i] != '\n') ++i;
19 size_t end = i;
20 if (end > start && src[end - 1] == '\r') --end;
21 out.s = src + start;
22 out.len = static_cast<uint16_t>(end - start);
23 return (i < n) ? i + 1 : n;
24}
25
27Span trimmed(Span raw, uint16_t& leading) {
28 uint16_t a = 0;
29 while (a < raw.len && isSpace(raw.s[a])) ++a;
30 uint16_t b = raw.len;
31 while (b > a && isSpace(raw.s[b - 1])) --b;
32 leading = a;
33 return Span{raw.s + a, static_cast<uint16_t>(b - a)};
34}
35
38uint8_t headingLevel(Span t) {
39 uint8_t h = 0;
40 while (h < t.len && t.s[h] == '#') ++h;
41 if (h >= 1 && h <= 6 && (h == t.len || t.s[h] == ' ')) return h;
42 return 0;
43}
44
46bool isRule(Span t) {
47 if (t.len < 3) return false;
48 char marker = 0;
49 int count = 0;
50 for (uint16_t i = 0; i < t.len; ++i) {
51 char c = t.s[i];
52 if (c == ' ') continue;
53 if (c != '-' && c != '*' && c != '_') return false;
54 if (marker == 0) marker = c;
55 else if (c != marker) return false;
56 ++count;
57 }
58 return count >= 3;
59}
60
61bool isUnorderedMarker(Span t) {
62 return t.len >= 2 && (t.s[0] == '-' || t.s[0] == '*' || t.s[0] == '+') && t.s[1] == ' ';
63}
64
66uint16_t orderedMarkerLen(Span t) {
67 uint16_t i = 0;
68 while (i < t.len && t.s[i] >= '0' && t.s[i] <= '9') ++i;
69 if (i == 0 || i + 1 >= t.len) return 0;
70 if ((t.s[i] == '.' || t.s[i] == ')') && t.s[i + 1] == ' ') return static_cast<uint16_t>(i + 2);
71 return 0;
72}
73
74bool isFenceDelim(Span t) {
75 return t.len >= 3 &&
76 ((t.s[0] == '`' && t.s[1] == '`' && t.s[2] == '`') ||
77 (t.s[0] == '~' && t.s[1] == '~' && t.s[2] == '~'));
78}
79
81Span headingText(Span t, uint8_t level) {
82 uint16_t a = level;
83 if (a < t.len && t.s[a] == ' ') ++a;
84 uint16_t b = t.len;
85 while (b > a && (t.s[b - 1] == '#' || t.s[b - 1] == ' ')) --b;
86 return Span{t.s + a, static_cast<uint16_t>(b - a)};
87}
88
89} // namespace
90
91MarkdownParseResult parseMarkdown(const char* src, size_t len, StyledLineSink& sink, size_t maxBytes) {
93 size_t n = len;
94 if (n > maxBytes) {
95 n = maxBytes;
96 result.truncated = true;
97 }
98 if (!src) return result;
99
100 // Pass 1: collect distinct heading levels (ignoring fenced code) and map
101 // each present level to a font, smallest font for the least-prominent level.
102 bool present[7] = {false, false, false, false, false, false, false};
103 {
104 bool fence = false;
105 size_t pos = 0;
106 Span raw;
107 while (pos < n) {
108 pos = nextLine(src, n, pos, raw);
109 uint16_t lead = 0;
110 Span t = trimmed(raw, lead);
111 if (isFenceDelim(t)) { fence = !fence; continue; }
112 if (fence) continue;
113 uint8_t lvl = headingLevel(t);
114 if (lvl) present[lvl] = true;
115 }
116 }
117
120 {
121 const uint8_t fonts[3] = {mdfont::Bold9pt, mdfont::Bold12pt, mdfont::Bold18pt};
122 int idx = 0;
123 for (int lvl = 6; lvl >= 1; --lvl) {
124 if (!present[lvl]) continue;
125 levelFont[lvl] = fonts[idx < 3 ? idx : 2];
126 ++idx;
127 }
128 }
129
130 // Pass 2: classify and emit each line.
131 bool fence = false;
132 size_t pos = 0;
133 Span raw;
134 while (pos < n) {
135 pos = nextLine(src, n, pos, raw);
136 uint16_t lead = 0;
137 Span t = trimmed(raw, lead);
138
139 StyledLine line;
140
141 if (isFenceDelim(t)) {
142 fence = !fence;
143 continue; // delimiters are not rendered
144 }
145 if (fence) {
146 line.kind = MdLineKind::Code;
147 line.inverted = true;
148 line.text = raw.s;
149 line.len = raw.len;
150 sink.emit(line);
151 ++result.lineCount;
152 continue;
153 }
154 if (t.len == 0) {
155 line.kind = MdLineKind::Blank;
156 line.text = t.s;
157 line.len = 0;
158 } else if (uint8_t lvl = headingLevel(t)) {
159 Span h = headingText(t, lvl);
161 line.font = levelFont[lvl];
162 line.text = h.s;
163 line.len = h.len;
164 } else if (isRule(t)) {
165 line.kind = MdLineKind::Rule;
166 line.text = t.s;
167 line.len = 0;
168 } else if (t.s[0] == '>') {
169 uint16_t a = 1;
170 if (a < t.len && t.s[a] == ' ') ++a;
171 line.kind = MdLineKind::Quote;
172 line.text = t.s + a;
173 line.len = static_cast<uint16_t>(t.len - a);
174 } else if (uint16_t om = orderedMarkerLen(t)) {
176 line.indent = static_cast<uint8_t>(lead / 2);
177 line.text = t.s + om;
178 line.len = static_cast<uint16_t>(t.len - om);
179 } else if (isUnorderedMarker(t)) {
181 line.indent = static_cast<uint8_t>(lead / 2);
182 line.text = t.s + 2;
183 line.len = static_cast<uint16_t>(t.len - 2);
184 } else if (lead >= 4) {
185 line.kind = MdLineKind::Code;
186 line.inverted = true;
187 line.text = raw.s + 4;
188 line.len = static_cast<uint16_t>(raw.len - 4);
189 } else {
191 line.text = t.s;
192 line.len = t.len;
193 }
194
195 sink.emit(line);
196 ++result.lineCount;
197 }
198
199 return result;
200}
201
202} // namespace cdc::ui
Centralized key-code constants for cdc_views.
Definition IModule.h:8
MarkdownParseResult parseMarkdown(const char *src, size_t len, StyledLineSink &sink, size_t maxBytes)
Parses Markdown source into styled logical lines via a sink.
Outcome of a parse run.
bool truncated
True when the source exceeded maxBytes.
uint16_t lineCount
Number of styled lines emitted.
Sink the parser emits styled lines to (decouples model from storage).
virtual void emit(const StyledLine &line)=0
One rendered logical line produced by the Markdown parser.
uint16_t len
Span length in bytes.
uint8_t font
One of mdfont.
uint8_t indent
Nesting depth (view multiplies by columns).
const char * text
Span into source (not null-terminated).
bool inverted
Inverse background (code).