Add Smartypants support for French Guillemets
This commits adds flag `HTML_SMARTYPANTS_QUOTES_NBSP` which, when combined with `HTML_USE_SMARTYPANTS` will insert non-breaking spaces between the double quotes and the contained text. This is mostly relevant for use in French with `HTML_SMARTYPANTS_ANGLED_QUOTES`. It should not hurt existing code path in the performance department: ``` name old time/op new time/op delta SmartDoubleQuotes-4 2.58µs ± 1% 2.58µs ± 1% ~ (p=1.000 n=5+5) name old alloc/op new alloc/op delta SmartDoubleQuotes-4 5.27kB ± 0% 5.27kB ± 0% ~ (all samples are equal) name old allocs/op new allocs/op delta SmartDoubleQuotes-4 13.0 ± 0% 13.0 ± 0% ~ (all samples are equal) ``` Fixes #378pull/379/head
parent
067529f716
commit
4ca8c28b21
1
html.go
1
html.go
|
@ -42,6 +42,7 @@ const (
|
||||||
HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS)
|
HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS)
|
||||||
HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
|
HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
|
||||||
HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
|
HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
|
||||||
|
HTML_SMARTYPANTS_QUOTES_NBSP // enable "French guillemets" (with HTML_USE_SMARTYPANTS)
|
||||||
HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source
|
HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1173,6 +1173,18 @@ func TestSmartDoubleQuotes(t *testing.T) {
|
||||||
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
|
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSmartDoubleQuotesNbsp(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
"this should be normal \"quoted\" text.\n",
|
||||||
|
"<p>this should be normal “ quoted ” text.</p>\n",
|
||||||
|
"this \" single double\n",
|
||||||
|
"<p>this “ single double</p>\n",
|
||||||
|
"two pair of \"some\" quoted \"text\".\n",
|
||||||
|
"<p>two pair of “ some ” quoted “ text ”.</p>\n"}
|
||||||
|
|
||||||
|
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_QUOTES_NBSP, HtmlRendererParameters{})
|
||||||
|
}
|
||||||
|
|
||||||
func TestSmartAngledDoubleQuotes(t *testing.T) {
|
func TestSmartAngledDoubleQuotes(t *testing.T) {
|
||||||
var tests = []string{
|
var tests = []string{
|
||||||
"this should be angled \"quoted\" text.\n",
|
"this should be angled \"quoted\" text.\n",
|
||||||
|
@ -1185,6 +1197,18 @@ func TestSmartAngledDoubleQuotes(t *testing.T) {
|
||||||
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{})
|
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSmartAngledDoubleQuotesNbsp(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
"this should be angled \"quoted\" text.\n",
|
||||||
|
"<p>this should be angled « quoted » text.</p>\n",
|
||||||
|
"this \" single double\n",
|
||||||
|
"<p>this « single double</p>\n",
|
||||||
|
"two pair of \"some\" quoted \"text\".\n",
|
||||||
|
"<p>two pair of « some » quoted « text ».</p>\n"}
|
||||||
|
|
||||||
|
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES|HTML_SMARTYPANTS_QUOTES_NBSP, HtmlRendererParameters{})
|
||||||
|
}
|
||||||
|
|
||||||
func TestSmartFractions(t *testing.T) {
|
func TestSmartFractions(t *testing.T) {
|
||||||
var tests = []string{
|
var tests = []string{
|
||||||
"1/2, 1/4 and 3/4; 1/4th and 3/4ths\n",
|
"1/2, 1/4 and 3/4; 1/4th and 3/4ths\n",
|
||||||
|
@ -1240,3 +1264,9 @@ func TestDisableSmartDashes(t *testing.T) {
|
||||||
HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_LATEX_DASHES,
|
HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_LATEX_DASHES,
|
||||||
HtmlRendererParameters{})
|
HtmlRendererParameters{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkSmartDoubleQuotes(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
runMarkdownInline("this should be normal \"quoted\" text.\n", Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func isdigit(c byte) bool {
|
||||||
return c >= '0' && c <= '9'
|
return c >= '0' && c <= '9'
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool {
|
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool {
|
||||||
// edge of the buffer is likely to be a tag that we don't get to see,
|
// edge of the buffer is likely to be a tag that we don't get to see,
|
||||||
// so we treat it like text sometimes
|
// so we treat it like text sometimes
|
||||||
|
|
||||||
|
@ -96,6 +96,12 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
|
||||||
*isOpen = false
|
*isOpen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that with the limited lookahead, this non-breaking
|
||||||
|
// space will also be appended to single double quotes.
|
||||||
|
if addNBSP && !*isOpen {
|
||||||
|
out.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
out.WriteByte('&')
|
out.WriteByte('&')
|
||||||
if *isOpen {
|
if *isOpen {
|
||||||
out.WriteByte('l')
|
out.WriteByte('l')
|
||||||
|
@ -104,6 +110,11 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
|
||||||
}
|
}
|
||||||
out.WriteByte(quote)
|
out.WriteByte(quote)
|
||||||
out.WriteString("quo;")
|
out.WriteString("quo;")
|
||||||
|
|
||||||
|
if addNBSP && *isOpen {
|
||||||
|
out.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +127,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
|
||||||
if len(text) >= 3 {
|
if len(text) >= 3 {
|
||||||
nextChar = text[2]
|
nextChar = text[2]
|
||||||
}
|
}
|
||||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +152,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
|
||||||
if len(text) > 1 {
|
if len(text) > 1 {
|
||||||
nextChar = text[1]
|
nextChar = text[1]
|
||||||
}
|
}
|
||||||
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) {
|
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote, false) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,13 +216,13 @@ func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
|
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte, addNBSP bool) int {
|
||||||
if bytes.HasPrefix(text, []byte(""")) {
|
if bytes.HasPrefix(text, []byte(""")) {
|
||||||
nextChar := byte(0)
|
nextChar := byte(0)
|
||||||
if len(text) >= 7 {
|
if len(text) >= 7 {
|
||||||
nextChar = text[6]
|
nextChar = text[6]
|
||||||
}
|
}
|
||||||
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, addNBSP) {
|
||||||
return 5
|
return 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,12 +235,15 @@ func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func smartAmp(angledQuotes, addNBSP bool) func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
return smartAmpVariant(out, smrt, previousChar, text, 'd')
|
var quote byte = 'd'
|
||||||
}
|
if angledQuotes {
|
||||||
|
quote = 'a'
|
||||||
|
}
|
||||||
|
|
||||||
func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
return func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
return smartAmpVariant(out, smrt, previousChar, text, 'a')
|
return smartAmpVariant(out, smrt, previousChar, text, quote, addNBSP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
@ -253,7 +267,7 @@ func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
|
||||||
if len(text) >= 3 {
|
if len(text) >= 3 {
|
||||||
nextChar = text[2]
|
nextChar = text[2]
|
||||||
}
|
}
|
||||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,7 +351,7 @@ func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousC
|
||||||
if len(text) > 1 {
|
if len(text) > 1 {
|
||||||
nextChar = text[1]
|
nextChar = text[1]
|
||||||
}
|
}
|
||||||
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, false) {
|
||||||
out.WriteString(""")
|
out.WriteString(""")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,14 +381,30 @@ type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar b
|
||||||
|
|
||||||
type smartypantsRenderer [256]smartCallback
|
type smartypantsRenderer [256]smartCallback
|
||||||
|
|
||||||
|
var (
|
||||||
|
smartAmpAngled = smartAmp(true, false)
|
||||||
|
smartAmpAngledNBSP = smartAmp(true, true)
|
||||||
|
smartAmpRegular = smartAmp(false, false)
|
||||||
|
smartAmpRegularNBSP = smartAmp(false, true)
|
||||||
|
)
|
||||||
|
|
||||||
func smartypants(flags int) *smartypantsRenderer {
|
func smartypants(flags int) *smartypantsRenderer {
|
||||||
r := new(smartypantsRenderer)
|
r := new(smartypantsRenderer)
|
||||||
|
addNBSP := flags&HTML_SMARTYPANTS_QUOTES_NBSP != 0
|
||||||
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
|
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
|
||||||
r['"'] = smartDoubleQuote
|
r['"'] = smartDoubleQuote
|
||||||
r['&'] = smartAmp
|
if !addNBSP {
|
||||||
|
r['&'] = smartAmpRegular
|
||||||
|
} else {
|
||||||
|
r['&'] = smartAmpRegularNBSP
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
r['"'] = smartAngledDoubleQuote
|
r['"'] = smartAngledDoubleQuote
|
||||||
r['&'] = smartAmpAngledQuote
|
if !addNBSP {
|
||||||
|
r['&'] = smartAmpAngled
|
||||||
|
} else {
|
||||||
|
r['&'] = smartAmpAngledNBSP
|
||||||
|
}
|
||||||
}
|
}
|
||||||
r['\''] = smartSingleQuote
|
r['\''] = smartSingleQuote
|
||||||
r['('] = smartParens
|
r['('] = smartParens
|
||||||
|
|
Loading…
Reference in New Issue