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