diff --git a/cmd/mdv-gui/app.go b/cmd/mdv-gui/app.go
index d57999e..d8a6d15 100644
--- a/cmd/mdv-gui/app.go
+++ b/cmd/mdv-gui/app.go
@@ -61,7 +61,7 @@ func (a *App) load(path string) error {
if err != nil {
return err
}
- htmlBytes, err := render.ToHTML(b, a.cfg.GUITheme, a.cfg.GUIThemeLight, a.cfg.GUIThemeDark, a.cfg.GUIWidth)
+ htmlBytes, err := render.ToHTML(b, a.cfg.GUITheme, a.cfg.GUIThemeLight, a.cfg.GUIThemeDark, a.cfg.GUIWidth, path)
if err != nil {
return err
}
diff --git a/examples/images-test.md b/examples/images-test.md
new file mode 100644
index 0000000..a6559d6
--- /dev/null
+++ b/examples/images-test.md
@@ -0,0 +1,25 @@
+# Image Rendering Test
+
+This document tests local image rendering in mdv-gui.
+
+## Local Image References
+
+### Relative Path
+Here's an image using a relative path:
+
+
+
+### Another Reference
+This should display the mdv app icon:
+
+
+
+## External Images
+
+For comparison, here's an external image (should work without changes):
+
+
+
+## Test Results
+
+If you can see the local images above in mdv-gui, the feature is working correctly!
diff --git a/examples/images/test-icon.png b/examples/images/test-icon.png
new file mode 100644
index 0000000..22e0765
Binary files /dev/null and b/examples/images/test-icon.png differ
diff --git a/internal/render/golden_test.go b/internal/render/golden_test.go
index 6cbf14e..589dc8e 100644
--- a/internal/render/golden_test.go
+++ b/internal/render/golden_test.go
@@ -11,7 +11,7 @@ func TestToHTMLMatchesGolden(t *testing.T) {
src := []byte("# Title\n\nContent")
themePath := filepath.Join("testdata", "html_theme.css")
- out, err := ToHTML(src, themePath, "", "", "800")
+ out, err := ToHTML(src, themePath, "", "", "800", "")
if err != nil {
t.Fatalf("ToHTML returned error: %v", err)
}
diff --git a/internal/render/render.go b/internal/render/render.go
index 6778630..5a21de7 100644
--- a/internal/render/render.go
+++ b/internal/render/render.go
@@ -2,10 +2,12 @@ package render
import (
"bytes"
+ "encoding/base64"
"fmt"
"os"
"os/exec"
"path/filepath"
+ "regexp"
"runtime"
"strconv"
"strings"
@@ -122,7 +124,76 @@ func ResolveTheme(theme, themeLight, themeDark string) string {
return detected
}
-func ToHTML(src []byte, theme, themeLight, themeDark, width string) ([]byte, error) {
+// processLocalImages processes HTML and converts relative image paths to data URIs.
+// basePath is the directory of the markdown file, used to resolve relative paths.
+func processLocalImages(htmlContent string, basePath string) string {
+ // Regex to match img tags with src attributes
+ imgRegex := regexp.MustCompile(`]*?)src=["']([^"']+)["']([^>]*?)>`)
+
+ return imgRegex.ReplaceAllStringFunc(htmlContent, func(match string) string {
+ // Extract the src attribute value
+ srcRegex := regexp.MustCompile(`src=["']([^"']+)["']`)
+ srcMatches := srcRegex.FindStringSubmatch(match)
+ if len(srcMatches) < 2 {
+ return match
+ }
+
+ src := srcMatches[1]
+
+ // Skip if it's already a data URI or absolute URL
+ if strings.HasPrefix(src, "data:") ||
+ strings.HasPrefix(src, "http://") ||
+ strings.HasPrefix(src, "https://") ||
+ strings.HasPrefix(src, "//") {
+ return match
+ }
+
+ // Resolve relative path to absolute path
+ var absPath string
+ if filepath.IsAbs(src) {
+ absPath = src
+ } else {
+ absPath = filepath.Join(basePath, src)
+ }
+
+ // Read the image file
+ imgData, err := os.ReadFile(absPath)
+ if err != nil {
+ // If we can't read the file, return the original tag
+ return match
+ }
+
+ // Determine MIME type based on file extension
+ ext := strings.ToLower(filepath.Ext(absPath))
+ mimeType := "image/png" // default
+ switch ext {
+ case ".jpg", ".jpeg":
+ mimeType = "image/jpeg"
+ case ".png":
+ mimeType = "image/png"
+ case ".gif":
+ mimeType = "image/gif"
+ case ".svg":
+ mimeType = "image/svg+xml"
+ case ".webp":
+ mimeType = "image/webp"
+ case ".bmp":
+ mimeType = "image/bmp"
+ case ".ico":
+ mimeType = "image/x-icon"
+ }
+
+ // Encode to base64
+ encoded := base64.StdEncoding.EncodeToString(imgData)
+ dataURI := fmt.Sprintf("data:%s;base64,%s", mimeType, encoded)
+
+ // Replace src attribute with data URI
+ newMatch := srcRegex.ReplaceAllString(match, fmt.Sprintf(`src="%s"`, dataURI))
+ return newMatch
+ })
+}
+
+func ToHTML(src []byte, theme, themeLight, themeDark, width, mdFilePath string) ([]byte, error) {
// Convert markdown to HTML
var buf bytes.Buffer
if err := md.Convert(src, &buf); err != nil {
@@ -152,6 +223,13 @@ func ToHTML(src []byte, theme, themeLight, themeDark, width string) ([]byte, err
widthCSS = fmt.Sprintf(".markdown-body { max-width: %s; margin-left: auto !important; margin-right: auto !important; }\n", maxWidth)
}
+ // Process local images if we have a file path
+ htmlContent := buf.String()
+ if mdFilePath != "" {
+ basePath := filepath.Dir(mdFilePath)
+ htmlContent = processLocalImages(htmlContent, basePath)
+ }
+
// Wrap the HTML with the CSS theme and markdown-body container
var output bytes.Buffer
output.WriteString("\n")
output.WriteString(`