Define EastAsianLineBreaksStyle to specify behavior of line breaking

This commit is contained in:
OMOTO Tsukasa 2023-09-23 20:49:54 +09:00
parent dc2230c235
commit 9d0b1b6bb8
3 changed files with 75 additions and 60 deletions

View file

@ -9,31 +9,31 @@ import (
// A CJKOption sets options for CJK support mostly for HTML based renderers. // A CJKOption sets options for CJK support mostly for HTML based renderers.
type CJKOption func(*cjk) type CJKOption func(*cjk)
// A EastAsianLineBreaksOption sets options for east asian line breaks. // A EastAsianLineBreaksStyle is a style of east asian line breaks.
type EastAsianLineBreaksOption func(*eastAsianLineBreaks) type EastAsianLineBreaksStyle int
const (
EastAsianLineBreaksStyleSimple EastAsianLineBreaksStyle = iota
EastAsianLineBreaksCSS3Draft
)
type EastAsianLineBreaksFunction func()
// WithEastAsianLineBreaks is a functional option that indicates whether softline breaks // WithEastAsianLineBreaks is a functional option that indicates whether softline breaks
// between east asian wide characters should be ignored. // between east asian wide characters should be ignored.
func WithEastAsianLineBreaks(opts ...EastAsianLineBreaksOption) CJKOption { func WithEastAsianLineBreaks(style ...EastAsianLineBreaksStyle) CJKOption {
return func(c *cjk) { return func(c *cjk) {
e := &eastAsianLineBreaks{ e := &eastAsianLineBreaks{
Enabled: true, Enabled: true,
EastAsianLineBreaksStyle: EastAsianLineBreaksStyleSimple,
} }
for _, opt := range opts { for _, s := range style {
opt(e) e.EastAsianLineBreaksStyle = s
} }
c.EastAsianLineBreaks = e c.EastAsianLineBreaks = e
} }
} }
// WithWorksEvenWithOneSide is a functional option that indicates that a softline break
// is ignored even if only one side of the break is east asian wide character.
func WithWorksEvenWithOneSide() EastAsianLineBreaksOption {
return func(e *eastAsianLineBreaks) {
e.WorksEvenWithOneSide = true
}
}
// WithEscapedSpace is a functional option that indicates that a '\' escaped half-space(0x20) should not be rendered. // WithEscapedSpace is a functional option that indicates that a '\' escaped half-space(0x20) should not be rendered.
func WithEscapedSpace() CJKOption { func WithEscapedSpace() CJKOption {
return func(c *cjk) { return func(c *cjk) {
@ -47,8 +47,8 @@ type cjk struct {
} }
type eastAsianLineBreaks struct { type eastAsianLineBreaks struct {
Enabled bool Enabled bool
WorksEvenWithOneSide bool EastAsianLineBreaksStyle EastAsianLineBreaksStyle
} }
// CJK is a goldmark extension that provides functionalities for CJK languages. // CJK is a goldmark extension that provides functionalities for CJK languages.
@ -66,13 +66,13 @@ func NewCJK(opts ...CJKOption) goldmark.Extender {
func (e *cjk) Extend(m goldmark.Markdown) { func (e *cjk) Extend(m goldmark.Markdown) {
if e.EastAsianLineBreaks != nil { if e.EastAsianLineBreaks != nil {
if e.EastAsianLineBreaks.Enabled { if e.EastAsianLineBreaks.Enabled {
opts := []html.EastAsianLineBreaksOption{} style := html.EastAsianLineBreaksStyleSimple
if e.EastAsianLineBreaks.WorksEvenWithOneSide { switch e.EastAsianLineBreaks.EastAsianLineBreaksStyle {
opts = append(opts, html.WithWorksEvenWithOneSide()) case EastAsianLineBreaksCSS3Draft:
style = html.EastAsianLineBreaksCSS3Draft
} }
m.Renderer().AddOptions(html.WithEastAsianLineBreaks(opts...)) m.Renderer().AddOptions(html.WithEastAsianLineBreaks(style))
} }
} }
if e.EscapedSpace { if e.EscapedSpace {
m.Renderer().AddOptions(html.WithWriter(html.NewWriter(html.WithEscapedSpace()))) m.Renderer().AddOptions(html.WithWriter(html.NewWriter(html.WithEscapedSpace())))

View file

@ -209,13 +209,13 @@ func TestEastAsianLineBreaks(t *testing.T) {
t, t,
) )
// WithWorksEvenWithOneSide option // test with EastAsianLineBreaksCSS3Draft
markdown = goldmark.New(goldmark.WithRendererOptions( markdown = goldmark.New(goldmark.WithRendererOptions(
html.WithXHTML(), html.WithXHTML(),
html.WithUnsafe(), html.WithUnsafe(),
), ),
goldmark.WithExtensions( goldmark.WithExtensions(
NewCJK(WithEastAsianLineBreaks(WithWorksEvenWithOneSide())), NewCJK(WithEastAsianLineBreaks(EastAsianLineBreaksCSS3Draft)),
), ),
) )
no = 9 no = 9

View file

@ -103,52 +103,75 @@ func WithHardWraps() interface {
// EastAsianLineBreaks is an option name used in WithEastAsianLineBreaks. // EastAsianLineBreaks is an option name used in WithEastAsianLineBreaks.
const optEastAsianLineBreaks renderer.OptionName = "EastAsianLineBreaks" const optEastAsianLineBreaks renderer.OptionName = "EastAsianLineBreaks"
type EastAsianLineBreaksStyle int
const (
EastAsianLineBreaksStyleSimple EastAsianLineBreaksStyle = iota
EastAsianLineBreaksCSS3Draft
)
type eastAsianLineBreaksFunction interface {
SoftLineBreak(thisLastRune rune, siblingFirstRune rune) bool
}
type eastAsianLineBreaksSimple struct{}
func (e *eastAsianLineBreaksSimple) SoftLineBreak(thisLastRune rune, siblingFirstRune rune) bool {
return !(util.IsEastAsianWideRune(thisLastRune) && util.IsEastAsianWideRune(siblingFirstRune))
}
type eastAsianLineBreaksCSS3Draft struct{}
func (e *eastAsianLineBreaksCSS3Draft) SoftLineBreak(thisLastRune rune, siblingFirstRune rune) bool {
return !(util.IsEastAsianWideRune(thisLastRune) || util.IsEastAsianWideRune(siblingFirstRune))
}
type eastAsianLineBreaks struct { type eastAsianLineBreaks struct {
Enabled bool Enabled bool
WorksEvenWithOneSide bool EastAsianLineBreaksFunction eastAsianLineBreaksFunction
} }
type withEastAsianLineBreaks struct { type withEastAsianLineBreaks struct {
worksEvenWithOneSide bool eastAsianLineBreaksStyle EastAsianLineBreaksStyle
} }
// A EastAsianLineBreaksOption sets options for east asian line breaks.
type EastAsianLineBreaksOption func(*withEastAsianLineBreaks)
func (o *withEastAsianLineBreaks) SetConfig(c *renderer.Config) { func (o *withEastAsianLineBreaks) SetConfig(c *renderer.Config) {
c.Options[optEastAsianLineBreaks] = eastAsianLineBreaks{ switch o.eastAsianLineBreaksStyle {
Enabled: true, case EastAsianLineBreaksStyleSimple:
WorksEvenWithOneSide: o.worksEvenWithOneSide, c.Options[optEastAsianLineBreaks] = eastAsianLineBreaks{
Enabled: true,
EastAsianLineBreaksFunction: &eastAsianLineBreaksSimple{},
}
case EastAsianLineBreaksCSS3Draft:
c.Options[optEastAsianLineBreaks] = eastAsianLineBreaks{
Enabled: true,
EastAsianLineBreaksFunction: &eastAsianLineBreaksCSS3Draft{},
}
} }
} }
func (o *withEastAsianLineBreaks) SetHTMLOption(c *Config) { func (o *withEastAsianLineBreaks) SetHTMLOption(c *Config) {
c.EastAsianLineBreaks = eastAsianLineBreaks{ switch o.eastAsianLineBreaksStyle {
Enabled: true, case EastAsianLineBreaksStyleSimple:
WorksEvenWithOneSide: o.worksEvenWithOneSide, c.EastAsianLineBreaks = eastAsianLineBreaks{
Enabled: true,
EastAsianLineBreaksFunction: &eastAsianLineBreaksSimple{},
}
case EastAsianLineBreaksCSS3Draft:
c.EastAsianLineBreaks = eastAsianLineBreaks{
Enabled: true,
EastAsianLineBreaksFunction: &eastAsianLineBreaksCSS3Draft{},
}
} }
} }
// WithEastAsianLineBreaks is a functional option that indicates whether softline breaks // WithEastAsianLineBreaks is a functional option that indicates whether softline breaks
// between east asian wide characters should be ignored. // between east asian wide characters should be ignored.
func WithEastAsianLineBreaks(opts ...EastAsianLineBreaksOption) interface { func WithEastAsianLineBreaks(style EastAsianLineBreaksStyle) interface {
renderer.Option renderer.Option
Option Option
} { } {
w := &withEastAsianLineBreaks{} return &withEastAsianLineBreaks{style}
for _, opt := range opts {
opt(w)
}
return w
}
// WithWorksEvenWithOneSide is a functional option that indicates that a softline break
// is ignored even if only one side of the break is east asian wide character.
func WithWorksEvenWithOneSide() EastAsianLineBreaksOption {
return func(o *withEastAsianLineBreaks) {
o.worksEvenWithOneSide = true
}
} }
// XHTML is an option name used in WithXHTML. // XHTML is an option name used in WithXHTML.
@ -697,16 +720,8 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, en
if siblingText := sibling.(*ast.Text).Text(source); len(siblingText) != 0 { if siblingText := sibling.(*ast.Text).Text(source); len(siblingText) != 0 {
thisLastRune := util.ToRune(value, len(value)-1) thisLastRune := util.ToRune(value, len(value)-1)
siblingFirstRune, _ := utf8.DecodeRune(siblingText) siblingFirstRune, _ := utf8.DecodeRune(siblingText)
if r.EastAsianLineBreaks.WorksEvenWithOneSide { if r.EastAsianLineBreaks.EastAsianLineBreaksFunction.SoftLineBreak(thisLastRune, siblingFirstRune) {
if !(util.IsEastAsianWideRune(thisLastRune) || _ = w.WriteByte('\n')
util.IsEastAsianWideRune(siblingFirstRune)) {
_ = w.WriteByte('\n')
}
} else {
if !(util.IsEastAsianWideRune(thisLastRune) &&
util.IsEastAsianWideRune(siblingFirstRune)) {
_ = w.WriteByte('\n')
}
} }
} }
} }