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  }