github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/present/style.go (about) 1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package present 6 7 import ( 8 "bytes" 9 "html" 10 "html/template" 11 "strings" 12 "unicode" 13 "unicode/utf8" 14 ) 15 16 /* 17 Fonts are demarcated by an initial and final char bracketing a 18 space-delimited word, plus possibly some terminal punctuation. 19 The chars are 20 _ for italic 21 * for bold 22 ` (back quote) for fixed width. 23 Inner appearances of the char become spaces. For instance, 24 _this_is_italic_! 25 becomes 26 <i>this is italic</i>! 27 */ 28 29 func init() { 30 funcs["style"] = Style 31 } 32 33 // Style returns s with HTML entities escaped and font indicators turned into 34 // HTML font tags. 35 func Style(s string) template.HTML { 36 return template.HTML(font(html.EscapeString(s))) 37 } 38 39 // font returns s with font indicators turned into HTML font tags. 40 func font(s string) string { 41 if strings.IndexAny(s, "[`_*") == -1 { 42 return s 43 } 44 words := split(s) 45 var b bytes.Buffer 46 Word: 47 for w, word := range words { 48 if len(word) < 2 { 49 continue Word 50 } 51 if link, _ := parseInlineLink(word); link != "" { 52 words[w] = link 53 continue Word 54 } 55 const punctuation = `.,;:()!?—–'"` 56 const marker = "_*`" 57 // Initial punctuation is OK but must be peeled off. 58 first := strings.IndexAny(word, marker) 59 if first == -1 { 60 continue Word 61 } 62 // Is the marker prefixed only by punctuation? 63 for _, r := range word[:first] { 64 if !strings.ContainsRune(punctuation, r) { 65 continue Word 66 } 67 } 68 open, word := word[:first], word[first:] 69 char := word[0] // ASCII is OK. 70 close := "" 71 switch char { 72 default: 73 continue Word 74 case '_': 75 open += "<i>" 76 close = "</i>" 77 case '*': 78 open += "<b>" 79 close = "</b>" 80 case '`': 81 open += "<code>" 82 close = "</code>" 83 } 84 // Terminal punctuation is OK but must be peeled off. 85 last := strings.LastIndex(word, word[:1]) 86 if last == 0 { 87 continue Word 88 } 89 head, tail := word[:last+1], word[last+1:] 90 for _, r := range tail { 91 if !strings.ContainsRune(punctuation, r) { 92 continue Word 93 } 94 } 95 b.Reset() 96 b.WriteString(open) 97 var wid int 98 for i := 1; i < len(head)-1; i += wid { 99 var r rune 100 r, wid = utf8.DecodeRuneInString(head[i:]) 101 if r != rune(char) { 102 // Ordinary character. 103 b.WriteRune(r) 104 continue 105 } 106 if head[i+1] != char { 107 // Inner char becomes space. 108 b.WriteRune(' ') 109 continue 110 } 111 // Doubled char becomes real char. 112 // Not worth worrying about "_x__". 113 b.WriteByte(char) 114 wid++ // Consumed two chars, both ASCII. 115 } 116 b.WriteString(close) // Write closing tag. 117 b.WriteString(tail) // Restore trailing punctuation. 118 words[w] = b.String() 119 } 120 return strings.Join(words, "") 121 } 122 123 // split is like strings.Fields but also returns the runs of spaces 124 // and treats inline links as distinct words. 125 func split(s string) []string { 126 var ( 127 words = make([]string, 0, 10) 128 start = 0 129 ) 130 131 // appendWord appends the string s[start:end] to the words slice. 132 // If the word contains the beginning of a link, the non-link portion 133 // of the word and the entire link are appended as separate words, 134 // and the start index is advanced to the end of the link. 135 appendWord := func(end int) { 136 if j := strings.Index(s[start:end], "[["); j > -1 { 137 if _, l := parseInlineLink(s[start+j:]); l > 0 { 138 // Append portion before link, if any. 139 if j > 0 { 140 words = append(words, s[start:start+j]) 141 } 142 // Append link itself. 143 words = append(words, s[start+j:start+j+l]) 144 // Advance start index to end of link. 145 start = start + j + l 146 return 147 } 148 } 149 // No link; just add the word. 150 words = append(words, s[start:end]) 151 start = end 152 } 153 154 wasSpace := false 155 for i, r := range s { 156 isSpace := unicode.IsSpace(r) 157 if i > start && isSpace != wasSpace { 158 appendWord(i) 159 } 160 wasSpace = isSpace 161 } 162 for start < len(s) { 163 appendWord(len(s)) 164 } 165 return words 166 }