From 2ea376671b1975392252bc6f70497c54d4bbab16 Mon Sep 17 00:00:00 2001 From: Shulhan Date: Tue, 15 Oct 2024 12:36:20 +0700 Subject: all: group all image related method and functions into single file This is to make it easy to see how it parsed and how it written to HTML, make the code more searchable. While at it, add test for block image. --- _doc/SPECS.adoc | 12 ++- element.go | 94 ----------------------- element_image.go | 163 ++++++++++++++++++++++++++++++++++++++++ element_image_test.go | 38 ++++++++++ html_backend.go | 41 ---------- inline_parser.go | 30 -------- testdata/element_image_test.txt | 35 +++++++++ 7 files changed, 246 insertions(+), 167 deletions(-) create mode 100644 element_image.go create mode 100644 element_image_test.go create mode 100644 testdata/element_image_test.txt diff --git a/_doc/SPECS.adoc b/_doc/SPECS.adoc index 20567ee..55e2615 100644 --- a/_doc/SPECS.adoc +++ b/_doc/SPECS.adoc @@ -346,12 +346,13 @@ ABSOLUTE_PATH = "/" WORD *( "/" WORD ) RELATIVE_PATH = ( "." / ".." ) "/" WORD * ( "/" WORD ) ---- + == Images -=== Inline image +=== Block image ---- -IMAGE_INLINE = "image:" URL "[" (IMAGE_ATTRS) "]" +BLOCK_IMAGE = "image::" URL "[" IMAGE_ATTRS "]" IMAGE_ATTRS = TEXT ("," IMAGE_WIDTH ("," IMAGE_HEIGHT)) *("," IMAGE_OPTS) @@ -360,6 +361,13 @@ IMAGE_OPTS = IMAGE_OPT_KEY "=" 1*VCHAR IMAGE_OPT_KEY = "title" / "float" / "align" / "role" / "link" ---- +=== Inline image + +---- +IMAGE_INLINE = "image:" URL "[" (IMAGE_ATTRS) "]" +---- + + == Video ---- diff --git a/element.go b/element.go index 498524c..9f77633 100644 --- a/element.go +++ b/element.go @@ -301,100 +301,6 @@ func (el *element) parseBlockAudio(doc *Document, line []byte) bool { return true } -// parseBlockImage parse the image block or line. -// The line parameter must not have "image::" block or "image:" macro prefix. -func (el *element) parseBlockImage(doc *Document, line []byte) bool { - var ( - attrBegin = bytes.IndexByte(line, '[') - - attr string - key string - val string - kv []string - - attrs [][]byte - src []byte - battr []byte - alt []byte - - attrEnd int - dot int - x int - ) - - if attrBegin < 0 { - return false - } - attrEnd = bytes.IndexByte(line, ']') - if attrEnd < 0 { - return false - } - - src = bytes.TrimRight(line[:attrBegin], " \t") - - if el.Attrs == nil { - el.Attrs = make(map[string]string) - } - src = applySubstitutions(doc, src) - el.Attrs[attrNameSrc] = string(src) - - attrs = bytes.Split(line[attrBegin+1:attrEnd], []byte(`,`)) - if el.Attrs == nil { - el.Attrs = make(map[string]string) - } - var hasWidth bool - for x, battr = range attrs { - attr = string(battr) - if x == 0 { - alt = bytes.TrimSpace(attrs[0]) - if len(alt) == 0 { - dot = bytes.IndexByte(src, '.') - if dot > 0 { - alt = src[:dot] - } - } - el.Attrs[attrNameAlt] = string(alt) - continue - } - if x == 1 { - if ascii.IsDigits(attrs[1]) { - el.Attrs[attrNameWidth] = string(attrs[1]) - hasWidth = true - continue - } - } - if hasWidth && x == 2 { - if ascii.IsDigits(attrs[2]) { - el.Attrs[attrNameHeight] = string(attrs[2]) - } - } - kv = strings.SplitN(attr, `=`, 2) - if len(kv) != 2 { - continue - } - key = strings.TrimSpace(kv[0]) - val = strings.Trim(kv[1], `"`) - switch key { - case attrNameFloat, attrNameAlign, attrNameRole: - if val == `center` { - val = `text-center` - } - el.addRole(val) - default: - el.Attrs[key] = val - } - } - - for key, val = range el.Attrs { - if key == attrNameLink { - val = string(applySubstitutions(doc, []byte(val))) - el.Attrs[key] = val - } - } - - return true -} - func (el *element) parseInlineMarkup(doc *Document, kind int) { if len(el.raw) == 0 { return diff --git a/element_image.go b/element_image.go new file mode 100644 index 0000000..d98bb61 --- /dev/null +++ b/element_image.go @@ -0,0 +1,163 @@ +// SPDX-FileCopyrightText: 2024 M. Shulhan +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package asciidoctor + +import ( + "bytes" + "fmt" + "io" + "strings" + + "git.sr.ht/~shulhan/pakakeh.go/lib/ascii" +) + +// parseBlockImage parse the image block or line. +// The line parameter must not contains the "image::" block or "image:" +// macro prefix. +func (el *element) parseBlockImage(doc *Document, line []byte) bool { + var attrBegin = bytes.IndexByte(line, '[') + if attrBegin < 0 { + return false + } + var attrEnd = bytes.IndexByte(line, ']') + if attrEnd < 0 { + return false + } + + var link = bytes.TrimRight(line[:attrBegin], " \t") + link = applySubstitutions(doc, link) + + if el.Attrs == nil { + el.Attrs = make(map[string]string) + } + el.Attrs[attrNameSrc] = string(link) + + var listAttribute = bytes.Split(line[attrBegin+1:attrEnd], []byte(`,`)) + if el.Attrs == nil { + el.Attrs = make(map[string]string) + } + + var ( + x int + battr []byte + hasWidth bool + ) + for x, battr = range listAttribute { + if x == 0 { + var alt = bytes.TrimSpace(listAttribute[0]) + if len(alt) == 0 { + var dot = bytes.IndexByte(link, '.') + if dot > 0 { + alt = link[:dot] + } + } + el.Attrs[attrNameAlt] = string(alt) + continue + } + if x == 1 { + if ascii.IsDigits(listAttribute[1]) { + el.Attrs[attrNameWidth] = string(listAttribute[1]) + hasWidth = true + continue + } + } + if hasWidth && x == 2 { + if ascii.IsDigits(listAttribute[2]) { + el.Attrs[attrNameHeight] = string(listAttribute[2]) + } + } + + var attr = string(battr) + var kv = strings.SplitN(attr, `=`, 2) + if len(kv) != 2 { + continue + } + var key = strings.TrimSpace(kv[0]) + var val = strings.Trim(kv[1], `"`) + + switch key { + case attrNameFloat, attrNameAlign, attrNameRole: + if val == `center` { + val = `text-center` + } + el.addRole(val) + + case attrNameLink: + val = string(applySubstitutions(doc, []byte(val))) + el.Attrs[key] = val + + default: + el.Attrs[key] = val + } + } + return true +} + +func parseInlineImage(doc *Document, content []byte) (el *element, n int) { + // If the next character is ':' (as in block "image::") mark it as + // invalid inline image, since this is block image that has been + // parsed but invalid (probably missing '[]'). + if content[0] == ':' { + return nil, 0 + } + + _, n = indexByteUnescape(content, ']') + if n < 0 { + return nil, 0 + } + + var lineImage = content[:n+1] + el = &element{ + elementAttribute: elementAttribute{ + Attrs: make(map[string]string), + }, + kind: elKindInlineImage, + } + if el.parseBlockImage(doc, lineImage) { + return el, n + 2 + } + return nil, 0 +} + +func htmlWriteInlineImage(el *element, out io.Writer) { + var ( + classes = strings.TrimSpace(`image ` + el.htmlClasses()) + + link string + withLink bool + ) + + fmt.Fprintf(out, ``, classes) + link, withLink = el.Attrs[attrNameLink] + if withLink { + fmt.Fprintf(out, ``, attrValueImage, link) + } + + var ( + src = el.Attrs[attrNameSrc] + alt = el.Attrs[attrNameAlt] + + width string + height string + ok bool + ) + + width, ok = el.Attrs[attrNameWidth] + if ok { + width = fmt.Sprintf(` width="%s"`, width) + } + height, ok = el.Attrs[attrNameHeight] + if ok { + height = fmt.Sprintf(` height="%s"`, height) + } + + fmt.Fprintf(out, `%q%s%s`, src, alt, width, height) + + if withLink { + fmt.Fprint(out, ``) + } + + fmt.Fprint(out, ``) +} diff --git a/element_image_test.go b/element_image_test.go new file mode 100644 index 0000000..a548501 --- /dev/null +++ b/element_image_test.go @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 M. Shulhan +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package asciidoctor + +import ( + "bytes" + "testing" + + "git.sr.ht/~shulhan/pakakeh.go/lib/test" +) + +func TestElementParseBlockImage(t *testing.T) { + var ( + tdata *test.Data + err error + ) + tdata, err = test.LoadData(`testdata/element_image_test.txt`) + if err != nil { + t.Fatal(err) + } + + var ( + caseName string + input []byte + got bytes.Buffer + ) + for caseName, input = range tdata.Input { + var doc = Parse(input) + + got.Reset() + doc.ToHTMLEmbedded(&got) + + var exp = string(tdata.Output[caseName]) + test.Assert(t, caseName, exp, got.String()) + } +} diff --git a/html_backend.go b/html_backend.go index b404ba7..14137e5 100644 --- a/html_backend.go +++ b/html_backend.go @@ -1063,47 +1063,6 @@ func htmlWriteHeader(doc *Document, out io.Writer) { fmt.Fprint(out, "\n") } -func htmlWriteInlineImage(el *element, out io.Writer) { - var ( - classes = strings.TrimSpace(`image ` + el.htmlClasses()) - - link string - withLink bool - ) - - fmt.Fprintf(out, ``, classes) - link, withLink = el.Attrs[attrNameLink] - if withLink { - fmt.Fprintf(out, ``, attrValueImage, link) - } - - var ( - src = el.Attrs[attrNameSrc] - alt = el.Attrs[attrNameAlt] - - width string - height string - ok bool - ) - - width, ok = el.Attrs[attrNameWidth] - if ok { - width = fmt.Sprintf(` width="%s"`, width) - } - height, ok = el.Attrs[attrNameHeight] - if ok { - height = fmt.Sprintf(` height="%s"`, height) - } - - fmt.Fprintf(out, `%q%s%s`, src, alt, width, height) - - if withLink { - fmt.Fprint(out, ``) - } - - fmt.Fprint(out, ``) -} - func htmlWriteInlinePass(doc *Document, el *element, out io.Writer) { var text = htmlSubs(doc, el) diff --git a/inline_parser.go b/inline_parser.go index 2f18aaf..561cab7 100644 --- a/inline_parser.go +++ b/inline_parser.go @@ -694,36 +694,6 @@ func (pi *inlineParser) parseFormatUnconstrained( return false } -func parseInlineImage(doc *Document, content []byte) (elImage *element, n int) { - var ( - lineImage []byte - ) - - // If the next character is ':' (as in block "image::") mark it as - // invalid inline image, since this is block image that has been - // parsed but invalid (probably missing '[]'). - if content[0] == ':' { - return nil, 0 - } - - _, n = indexByteUnescape(content, ']') - if n < 0 { - return nil, 0 - } - - lineImage = content[:n+1] - elImage = &element{ - elementAttribute: elementAttribute{ - Attrs: make(map[string]string), - }, - kind: elKindInlineImage, - } - if elImage.parseBlockImage(doc, lineImage) { - return elImage, n + 2 - } - return nil, 0 -} - func (pi *inlineParser) parseMacro() bool { var ( el *element diff --git a/testdata/element_image_test.txt b/testdata/element_image_test.txt new file mode 100644 index 0000000..ae15cbb --- /dev/null +++ b/testdata/element_image_test.txt @@ -0,0 +1,35 @@ +>>> empty_block_image + +image::[] + +<<< empty_block_image + +
+
+ +
+
+ +>>> with_absolute_path + +image::/absolute/path.png[Image alt text] + +<<< with_absolute_path + +
+
+Image alt text +
+
+ +>>> with_attr_link + +image::/image/path.png[Image alt text,link="{dummy}/link"] + +<<< with_attr_link + +
+
+Image alt text +
+
-- cgit v1.3