inline.go (view raw)
1//
2// Black Friday Markdown Processor
3// Originally based on http://github.com/tanoku/upskirt
4// by Russ Ross <russ@russross.com>
5//
6
7//
8// Functions to parse inline elements.
9//
10
11package blackfriday
12
13import (
14 "bytes"
15)
16
17// Functions to parse text within a block
18// Each function returns the number of chars taken care of
19// data is the complete block being rendered
20// offset is the number of valid chars before the current cursor
21
22func parseInline(out *bytes.Buffer, rndr *render, data []byte) {
23 // this is called recursively: enforce a maximum depth
24 if rndr.nesting >= rndr.maxNesting {
25 return
26 }
27 rndr.nesting++
28
29 i, end := 0, 0
30 for i < len(data) {
31 // copy inactive chars into the output
32 for end < len(data) && rndr.inline[data[end]] == nil {
33 end++
34 }
35
36 if rndr.mk.NormalText != nil {
37 rndr.mk.NormalText(out, data[i:end], rndr.mk.Opaque)
38 } else {
39 out.Write(data[i:end])
40 }
41
42 if end >= len(data) {
43 break
44 }
45 i = end
46
47 // call the trigger
48 parser := rndr.inline[data[end]]
49 if consumed := parser(out, rndr, data, i); consumed == 0 {
50 // no action from the callback; buffer the byte for later
51 end = i + 1
52 } else {
53 // skip past whatever the callback used
54 i += consumed
55 end = i
56 }
57 }
58
59 rndr.nesting--
60}
61
62// single and double emphasis parsing
63func inlineEmphasis(out *bytes.Buffer, rndr *render, data []byte, offset int) int {
64 data = data[offset:]
65 c := data[0]
66 ret := 0
67
68 if len(data) > 2 && data[1] != c {
69 // whitespace cannot follow an opening emphasis;
70 // strikethrough only takes two characters '~~'
71 if c == '~' || isspace(data[1]) {
72 return 0
73 }
74 if ret = inlineHelperEmph1(out, rndr, data[1:], c); ret == 0 {
75 return 0
76 }
77
78 return ret + 1
79 }
80
81 if len(data) > 3 && data[1] == c && data[2] != c {
82 if isspace(data[2]) {
83 return 0
84 }
85 if ret = inlineHelperEmph2(out, rndr, data[2:], c); ret == 0 {
86 return 0
87 }
88
89 return ret + 2
90 }
91
92 if len(data) > 4 && data[1] == c && data[2] == c && data[3] != c {
93 if c == '~' || isspace(data[3]) {
94 return 0
95 }
96 if ret = inlineHelperEmph3(out, rndr, data, 3, c); ret == 0 {
97 return 0
98 }
99
100 return ret + 3
101 }
102
103 return 0
104}
105
106func inlineCodeSpan(out *bytes.Buffer, rndr *render, data []byte, offset int) int {
107 data = data[offset:]
108
109 nb := 0
110
111 // count the number of backticks in the delimiter
112 for nb < len(data) && data[nb] == '`' {
113 nb++
114 }
115
116 // find the next delimiter
117 i, end := 0, 0
118 for end = nb; end < len(data) && i < nb; end++ {
119 if data[end] == '`' {
120 i++
121 } else {
122 i = 0
123 }
124 }
125
126 if i < nb && end >= len(data) {
127 out.WriteByte('`')
128 return 0 // no matching delimiter
129 }
130
131 // trim outside whitespace
132 f_begin := nb
133 for f_begin < end && (data[f_begin] == ' ' || data[f_begin] == '\t') {
134 f_begin++
135 }
136
137 f_end := end - nb
138 for f_end > nb && (data[f_end-1] == ' ' || data[f_end-1] == '\t') {
139 f_end--
140 }
141
142 // real code span
143 if rndr.mk.CodeSpan == nil {
144 return 0
145 }
146 if f_begin < f_end {
147 if rndr.mk.CodeSpan(out, data[f_begin:f_end], rndr.mk.Opaque) == 0 {
148 end = 0
149 }
150 } else {
151 if rndr.mk.CodeSpan(out, nil, rndr.mk.Opaque) == 0 {
152 end = 0
153 }
154 }
155
156 return end
157
158}
159
160// '\n' preceded by two spaces
161func inlineLineBreak(out *bytes.Buffer, rndr *render, data []byte, offset int) int {
162 if offset < 2 || data[offset-1] != ' ' || data[offset-2] != ' ' {
163 return 0
164 }
165
166 // remove trailing spaces from out and render
167 outBytes := out.Bytes()
168 end := len(outBytes)
169 for end > 0 && outBytes[end-1] == ' ' {
170 end--
171 }
172 out.Truncate(end)
173
174 if rndr.mk.LineBreak == nil {
175 return 0
176 }
177 if rndr.mk.LineBreak(out, rndr.mk.Opaque) > 0 {
178 return 1
179 } else {
180 return 0
181 }
182
183 return 0
184}
185
186// '[': parse a link or an image
187func inlineLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int {
188 isImg := offset > 0 && data[offset-1] == '!'
189
190 data = data[offset:]
191
192 i := 1
193 var title, link []byte
194 text_has_nl := false
195
196 // check whether the correct renderer exists
197 if (isImg && rndr.mk.Image == nil) || (!isImg && rndr.mk.Link == nil) {
198 return 0
199 }
200
201 // look for the matching closing bracket
202 for level := 1; level > 0 && i < len(data); i++ {
203 switch {
204 case data[i] == '\n':
205 text_has_nl = true
206
207 case data[i-1] == '\\':
208 continue
209
210 case data[i] == '[':
211 level++
212
213 case data[i] == ']':
214 level--
215 if level <= 0 {
216 i-- // compensate for extra i++ in for loop
217 }
218 }
219 }
220
221 if i >= len(data) {
222 return 0
223 }
224
225 txt_e := i
226 i++
227
228 // skip any amount of whitespace or newline
229 // (this is much more lax than original markdown syntax)
230 for i < len(data) && isspace(data[i]) {
231 i++
232 }
233
234 // inline style link
235 switch {
236 case i < len(data) && data[i] == '(':
237 // skip initial whitespace
238 i++
239
240 for i < len(data) && isspace(data[i]) {
241 i++
242 }
243
244 link_b := i
245
246 // look for link end: ' " )
247 for i < len(data) {
248 if data[i] == '\\' {
249 i += 2
250 } else {
251 if data[i] == ')' || data[i] == '\'' || data[i] == '"' {
252 break
253 }
254 i++
255 }
256 }
257
258 if i >= len(data) {
259 return 0
260 }
261 link_e := i
262
263 // look for title end if present
264 title_b, title_e := 0, 0
265 if data[i] == '\'' || data[i] == '"' {
266 i++
267 title_b = i
268
269 for i < len(data) {
270 if data[i] == '\\' {
271 i += 2
272 } else {
273 if data[i] == ')' {
274 break
275 }
276 i++
277 }
278 }
279
280 if i >= len(data) {
281 return 0
282 }
283
284 // skip whitespace after title
285 title_e = i - 1
286 for title_e > title_b && isspace(data[title_e]) {
287 title_e--
288 }
289
290 // check for closing quote presence
291 if data[title_e] != '\'' && data[title_e] != '"' {
292 title_b, title_e = 0, 0
293 link_e = i
294 }
295 }
296
297 // remove whitespace at the end of the link
298 for link_e > link_b && isspace(data[link_e-1]) {
299 link_e--
300 }
301
302 // remove optional angle brackets around the link
303 if data[link_b] == '<' {
304 link_b++
305 }
306 if data[link_e-1] == '>' {
307 link_e--
308 }
309
310 // build escaped link and title
311 if link_e > link_b {
312 link = data[link_b:link_e]
313 }
314
315 if title_e > title_b {
316 title = data[title_b:title_e]
317 }
318
319 i++
320
321 // reference style link
322 case i < len(data) && data[i] == '[':
323 var id []byte
324
325 // look for the id
326 i++
327 link_b := i
328 for i < len(data) && data[i] != ']' {
329 i++
330 }
331 if i >= len(data) {
332 return 0
333 }
334 link_e := i
335
336 // find the reference
337 if link_b == link_e {
338 if text_has_nl {
339 var b bytes.Buffer
340
341 for j := 1; j < txt_e; j++ {
342 switch {
343 case data[j] != '\n':
344 b.WriteByte(data[j])
345 case data[j-1] != ' ':
346 b.WriteByte(' ')
347 }
348 }
349
350 id = b.Bytes()
351 } else {
352 id = data[1:txt_e]
353 }
354 } else {
355 id = data[link_b:link_e]
356 }
357
358 // find the reference with matching id (ids are case-insensitive)
359 key := string(bytes.ToLower(id))
360 lr, ok := rndr.refs[key]
361 if !ok {
362 return 0
363 }
364
365 // keep link and title from reference
366 link = lr.link
367 title = lr.title
368 i++
369
370 // shortcut reference style link
371 default:
372 var id []byte
373
374 // craft the id
375 if text_has_nl {
376 var b bytes.Buffer
377
378 for j := 1; j < txt_e; j++ {
379 switch {
380 case data[j] != '\n':
381 b.WriteByte(data[j])
382 case data[j-1] != ' ':
383 b.WriteByte(' ')
384 }
385 }
386
387 id = b.Bytes()
388 } else {
389 id = data[1:txt_e]
390 }
391
392 // find the reference with matching id
393 key := string(bytes.ToLower(id))
394 lr, ok := rndr.refs[key]
395 if !ok {
396 return 0
397 }
398
399 // keep link and title from reference
400 link = lr.link
401 title = lr.title
402
403 // rewind the whitespace
404 i = txt_e + 1
405 }
406
407 // build content: img alt is escaped, link content is parsed
408 var content bytes.Buffer
409 if txt_e > 1 {
410 if isImg {
411 content.Write(data[1:txt_e])
412 } else {
413 parseInline(&content, rndr, data[1:txt_e])
414 }
415 }
416
417 var u_link []byte
418 if len(link) > 0 {
419 var u_link_buf bytes.Buffer
420 unescapeText(&u_link_buf, link)
421 u_link = u_link_buf.Bytes()
422 }
423
424 // call the relevant rendering function
425 ret := 0
426 if isImg {
427 outSize := out.Len()
428 outBytes := out.Bytes()
429 if outSize > 0 && outBytes[outSize-1] == '!' {
430 out.Truncate(outSize - 1)
431 }
432
433 ret = rndr.mk.Image(out, u_link, title, content.Bytes(), rndr.mk.Opaque)
434 } else {
435 ret = rndr.mk.Link(out, u_link, title, content.Bytes(), rndr.mk.Opaque)
436 }
437
438 if ret > 0 {
439 return i
440 }
441 return 0
442}
443
444// '<' when tags or autolinks are allowed
445func inlineLAngle(out *bytes.Buffer, rndr *render, data []byte, offset int) int {
446 data = data[offset:]
447 altype := LINK_TYPE_NOT_AUTOLINK
448 end := tagLength(data, &altype)
449 ret := 0
450
451 if end > 2 {
452 switch {
453 case rndr.mk.AutoLink != nil && altype != LINK_TYPE_NOT_AUTOLINK:
454 var u_link bytes.Buffer
455 unescapeText(&u_link, data[1:end+1-2])
456 ret = rndr.mk.AutoLink(out, u_link.Bytes(), altype, rndr.mk.Opaque)
457 case rndr.mk.RawHtmlTag != nil:
458 ret = rndr.mk.RawHtmlTag(out, data[:end], rndr.mk.Opaque)
459 }
460 }
461
462 if ret == 0 {
463 return 0
464 }
465 return end
466}
467
468// '\\' backslash escape
469var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>")
470
471func inlineEscape(out *bytes.Buffer, rndr *render, data []byte, offset int) int {
472 data = data[offset:]
473
474 if len(data) > 1 {
475 if bytes.IndexByte(escapeChars, data[1]) < 0 {
476 return 0
477 }
478
479 if rndr.mk.NormalText != nil {
480 rndr.mk.NormalText(out, data[1:2], rndr.mk.Opaque)
481 } else {
482 out.WriteByte(data[1])
483 }
484 }
485
486 return 2
487}
488
489func unescapeText(ob *bytes.Buffer, src []byte) {
490 i := 0
491 for i < len(src) {
492 org := i
493 for i < len(src) && src[i] != '\\' {
494 i++
495 }
496
497 if i > org {
498 ob.Write(src[org:i])
499 }
500
501 if i+1 >= len(src) {
502 break
503 }
504
505 ob.WriteByte(src[i+1])
506 i += 2
507 }
508}
509
510// '&' escaped when it doesn't belong to an entity
511// valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
512func inlineEntity(out *bytes.Buffer, rndr *render, data []byte, offset int) int {
513 data = data[offset:]
514
515 end := 1
516
517 if end < len(data) && data[end] == '#' {
518 end++
519 }
520
521 for end < len(data) && isalnum(data[end]) {
522 end++
523 }
524
525 if end < len(data) && data[end] == ';' {
526 end++ // real entity
527 } else {
528 return 0 // lone '&'
529 }
530
531 if rndr.mk.Entity != nil {
532 rndr.mk.Entity(out, data[:end], rndr.mk.Opaque)
533 } else {
534 out.Write(data[:end])
535 }
536
537 return end
538}
539
540func inlineAutoLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int {
541 // quick check to rule out most false hits on ':'
542 if len(data) < offset+3 || data[offset+1] != '/' || data[offset+2] != '/' {
543 return 0
544 }
545
546 // scan backward for a word boundary
547 rewind := 0
548 for offset-rewind > 0 && rewind <= 7 && !isspace(data[offset-rewind-1]) && !isspace(data[offset-rewind-1]) {
549 rewind++
550 }
551 if rewind > 6 { // longest supported protocol is "mailto" which has 6 letters
552 return 0
553 }
554
555 orig_data := data
556 data = data[offset-rewind:]
557
558 if !isSafeLink(data) {
559 return 0
560 }
561
562 link_end := 0
563 for link_end < len(data) && !isspace(data[link_end]) {
564 link_end++
565 }
566
567 // Skip punctuation at the end of the link
568 if (data[link_end-1] == '.' || data[link_end-1] == ',' || data[link_end-1] == ';') && data[link_end-2] != '\\' {
569 link_end--
570 }
571
572 // See if the link finishes with a punctuation sign that can be closed.
573 var copen byte
574 switch data[link_end-1] {
575 case '"':
576 copen = '"'
577 case '\'':
578 copen = '\''
579 case ')':
580 copen = '('
581 case ']':
582 copen = '['
583 case '}':
584 copen = '{'
585 default:
586 copen = 0
587 }
588
589 if copen != 0 {
590 buf_end := offset - rewind + link_end - 2
591
592 open_delim := 1
593
594 /* Try to close the final punctuation sign in this same line;
595 * if we managed to close it outside of the URL, that means that it's
596 * not part of the URL. If it closes inside the URL, that means it
597 * is part of the URL.
598 *
599 * Examples:
600 *
601 * foo http://www.pokemon.com/Pikachu_(Electric) bar
602 * => http://www.pokemon.com/Pikachu_(Electric)
603 *
604 * foo (http://www.pokemon.com/Pikachu_(Electric)) bar
605 * => http://www.pokemon.com/Pikachu_(Electric)
606 *
607 * foo http://www.pokemon.com/Pikachu_(Electric)) bar
608 * => http://www.pokemon.com/Pikachu_(Electric))
609 *
610 * (foo http://www.pokemon.com/Pikachu_(Electric)) bar
611 * => foo http://www.pokemon.com/Pikachu_(Electric)
612 */
613
614 for buf_end >= 0 && orig_data[buf_end] != '\n' && open_delim != 0 {
615 if orig_data[buf_end] == data[link_end-1] {
616 open_delim++
617 }
618
619 if orig_data[buf_end] == copen {
620 open_delim--
621 }
622
623 buf_end--
624 }
625
626 if open_delim == 0 {
627 link_end--
628 }
629 }
630
631 // we were triggered on the ':', so we need to rewind the output a bit
632 if out.Len() >= rewind {
633 out.Truncate(len(out.Bytes()) - rewind)
634 }
635
636 if rndr.mk.AutoLink != nil {
637 var u_link bytes.Buffer
638 unescapeText(&u_link, data[:link_end])
639
640 rndr.mk.AutoLink(out, u_link.Bytes(), LINK_TYPE_NORMAL, rndr.mk.Opaque)
641 }
642
643 return link_end - rewind
644}
645
646var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")}
647
648func isSafeLink(link []byte) bool {
649 for _, prefix := range validUris {
650 // TODO: handle unicode here
651 // case-insensitive prefix test
652 if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isalnum(link[len(prefix)]) {
653 return true
654 }
655 }
656
657 return false
658}
659
660// return the length of the given tag, or 0 is it's not valid
661func tagLength(data []byte, autolink *int) int {
662 var i, j int
663
664 // a valid tag can't be shorter than 3 chars
665 if len(data) < 3 {
666 return 0
667 }
668
669 // begins with a '<' optionally followed by '/', followed by letter or number
670 if data[0] != '<' {
671 return 0
672 }
673 if data[1] == '/' {
674 i = 2
675 } else {
676 i = 1
677 }
678
679 if !isalnum(data[i]) {
680 return 0
681 }
682
683 // scheme test
684 *autolink = LINK_TYPE_NOT_AUTOLINK
685
686 // try to find the beggining of an URI
687 for i < len(data) && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') {
688 i++
689 }
690
691 if i > 1 && data[i] == '@' {
692 if j = isMailtoAutoLink(data[i:]); j != 0 {
693 *autolink = LINK_TYPE_EMAIL
694 return i + j
695 }
696 }
697
698 if i > 2 && data[i] == ':' {
699 *autolink = LINK_TYPE_NORMAL
700 i++
701 }
702
703 // complete autolink test: no whitespace or ' or "
704 switch {
705 case i >= len(data):
706 *autolink = LINK_TYPE_NOT_AUTOLINK
707 case *autolink != 0:
708 j = i
709
710 for i < len(data) {
711 if data[i] == '\\' {
712 i += 2
713 } else {
714 if data[i] == '>' || data[i] == '\'' || data[i] == '"' || isspace(data[i]) {
715 break
716 } else {
717 i++
718 }
719 }
720
721 }
722
723 if i >= len(data) {
724 return 0
725 }
726 if i > j && data[i] == '>' {
727 return i + 1
728 }
729
730 // one of the forbidden chars has been found
731 *autolink = LINK_TYPE_NOT_AUTOLINK
732 }
733
734 // look for something looking like a tag end
735 for i < len(data) && data[i] != '>' {
736 i++
737 }
738 if i >= len(data) {
739 return 0
740 }
741 return i + 1
742}
743
744// look for the address part of a mail autolink and '>'
745// this is less strict than the original markdown e-mail address matching
746func isMailtoAutoLink(data []byte) int {
747 nb := 0
748
749 // address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@'
750 for i := 0; i < len(data); i++ {
751 if isalnum(data[i]) {
752 continue
753 }
754
755 switch data[i] {
756 case '@':
757 nb++
758
759 case '-', '.', '_':
760 break
761
762 case '>':
763 if nb == 1 {
764 return i + 1
765 } else {
766 return 0
767 }
768 default:
769 return 0
770 }
771 }
772
773 return 0
774}
775
776// look for the next emph char, skipping other constructs
777func inlineHelperFindEmphChar(data []byte, c byte) int {
778 i := 1
779
780 for i < len(data) {
781 for i < len(data) && data[i] != c && data[i] != '`' && data[i] != '[' {
782 i++
783 }
784 if i >= len(data) {
785 return 0
786 }
787 if data[i] == c {
788 return i
789 }
790
791 // do not count escaped chars
792 if i != 0 && data[i-1] == '\\' {
793 i++
794 continue
795 }
796
797 if data[i] == '`' {
798 // skip a code span
799 tmp_i := 0
800 i++
801 for i < len(data) && data[i] != '`' {
802 if tmp_i == 0 && data[i] == c {
803 tmp_i = i
804 }
805 i++
806 }
807 if i >= len(data) {
808 return tmp_i
809 }
810 i++
811 } else {
812 if data[i] == '[' {
813 // skip a link
814 tmp_i := 0
815 i++
816 for i < len(data) && data[i] != ']' {
817 if tmp_i == 0 && data[i] == c {
818 tmp_i = i
819 }
820 i++
821 }
822 i++
823 for i < len(data) && (data[i] == ' ' || data[i] == '\t' || data[i] == '\n') {
824 i++
825 }
826 if i >= len(data) {
827 return tmp_i
828 }
829 if data[i] != '[' && data[i] != '(' { // not a link
830 if tmp_i > 0 {
831 return tmp_i
832 } else {
833 continue
834 }
835 }
836 cc := data[i]
837 i++
838 for i < len(data) && data[i] != cc {
839 if tmp_i == 0 && data[i] == c {
840 tmp_i = i
841 }
842 i++
843 }
844 if i >= len(data) {
845 return tmp_i
846 }
847 i++
848 }
849 }
850 }
851 return 0
852}
853
854func inlineHelperEmph1(out *bytes.Buffer, rndr *render, data []byte, c byte) int {
855 i := 0
856
857 if rndr.mk.Emphasis == nil {
858 return 0
859 }
860
861 // skip one symbol if coming from emph3
862 if len(data) > 1 && data[0] == c && data[1] == c {
863 i = 1
864 }
865
866 for i < len(data) {
867 length := inlineHelperFindEmphChar(data[i:], c)
868 if length == 0 {
869 return 0
870 }
871 i += length
872 if i >= len(data) {
873 return 0
874 }
875
876 if i+1 < len(data) && data[i+1] == c {
877 i++
878 continue
879 }
880
881 if data[i] == c && !isspace(data[i-1]) {
882
883 if rndr.flags&EXTENSION_NO_INTRA_EMPHASIS != 0 {
884 if !(i+1 == len(data) || isspace(data[i+1]) || ispunct(data[i+1])) {
885 continue
886 }
887 }
888
889 var work bytes.Buffer
890 parseInline(&work, rndr, data[:i])
891 r := rndr.mk.Emphasis(out, work.Bytes(), rndr.mk.Opaque)
892 if r > 0 {
893 return i + 1
894 } else {
895 return 0
896 }
897 }
898 }
899
900 return 0
901}
902
903func inlineHelperEmph2(out *bytes.Buffer, rndr *render, data []byte, c byte) int {
904 render_method := rndr.mk.DoubleEmphasis
905 if c == '~' {
906 render_method = rndr.mk.StrikeThrough
907 }
908
909 if render_method == nil {
910 return 0
911 }
912
913 i := 0
914
915 for i < len(data) {
916 length := inlineHelperFindEmphChar(data[i:], c)
917 if length == 0 {
918 return 0
919 }
920 i += length
921
922 if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isspace(data[i-1]) {
923 var work bytes.Buffer
924 parseInline(&work, rndr, data[:i])
925 r := render_method(out, work.Bytes(), rndr.mk.Opaque)
926 if r > 0 {
927 return i + 2
928 } else {
929 return 0
930 }
931 }
932 i++
933 }
934 return 0
935}
936
937func inlineHelperEmph3(out *bytes.Buffer, rndr *render, data []byte, offset int, c byte) int {
938 i := 0
939 orig_data := data
940 data = data[offset:]
941
942 for i < len(data) {
943 length := inlineHelperFindEmphChar(data[i:], c)
944 if length == 0 {
945 return 0
946 }
947 i += length
948
949 // skip whitespace preceded symbols
950 if data[i] != c || isspace(data[i-1]) {
951 continue
952 }
953
954 switch {
955 case (i+2 < len(data) && data[i+1] == c && data[i+2] == c && rndr.mk.TripleEmphasis != nil):
956 // triple symbol found
957 var work bytes.Buffer
958
959 parseInline(&work, rndr, data[:i])
960 r := rndr.mk.TripleEmphasis(out, work.Bytes(), rndr.mk.Opaque)
961 if r > 0 {
962 return i + 3
963 } else {
964 return 0
965 }
966 case (i+1 < len(data) && data[i+1] == c):
967 // double symbol found, hand over to emph1
968 length = inlineHelperEmph1(out, rndr, orig_data[offset-2:], c)
969 if length == 0 {
970 return 0
971 } else {
972 return length - 2
973 }
974 default:
975 // single symbol found, hand over to emph2
976 length = inlineHelperEmph2(out, rndr, orig_data[offset-1:], c)
977 if length == 0 {
978 return 0
979 } else {
980 return length - 1
981 }
982 }
983 }
984 return 0
985}