| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- // Package exporter provides advanced export functionality
- package exporter
- import (
- "fmt"
- "os/exec"
- "runtime"
- "mermaid-go/pkg/ast"
- )
- // AdvancedExporter provides high-quality export using external tools
- type AdvancedExporter struct {
- svgExporter *SVGExporter
- }
- // NewAdvancedExporter creates a new advanced exporter
- func NewAdvancedExporter() *AdvancedExporter {
- return &AdvancedExporter{
- svgExporter: NewSVGExporter(),
- }
- }
- // ExportWithExternalTool exports using external tools for better quality
- func (e *AdvancedExporter) ExportWithExternalTool(diagram ast.Diagram, outputPath string, options *ExportOptions) error {
- if options == nil {
- options = DefaultExportOptions()
- }
- // First export to SVG
- e.svgExporter.SetSize(options.Width, options.Height)
- svgContent, err := e.svgExporter.ExportToSVG(diagram)
- if err != nil {
- return fmt.Errorf("failed to generate SVG: %w", err)
- }
- // If target is SVG, we're done
- if options.Format == FormatSVG {
- return writeFile(outputPath, []byte(svgContent))
- }
- // For PNG/JPEG, try to use external tools
- return e.convertSVGToRaster(svgContent, outputPath, options)
- }
- // convertSVGToRaster converts SVG to raster format using external tools
- func (e *AdvancedExporter) convertSVGToRaster(svgContent, outputPath string, options *ExportOptions) error {
- // Try different conversion methods in order of preference
- converters := []func(string, string, *ExportOptions) error{
- e.convertWithInkscape,
- e.convertWithImageMagick,
- e.convertWithRSVG,
- e.convertWithChrome,
- }
- var lastErr error
- for _, converter := range converters {
- if err := converter(svgContent, outputPath, options); err == nil {
- return nil
- } else {
- lastErr = err
- }
- }
- return fmt.Errorf("all conversion methods failed, last error: %w", lastErr)
- }
- // convertWithInkscape uses Inkscape for conversion
- func (e *AdvancedExporter) convertWithInkscape(svgContent, outputPath string, options *ExportOptions) error {
- if !e.isCommandAvailable("inkscape") {
- return fmt.Errorf("inkscape not available")
- }
- // Create temporary SVG file
- tempSVG := outputPath + ".temp.svg"
- if err := writeFile(tempSVG, []byte(svgContent)); err != nil {
- return err
- }
- defer removeFile(tempSVG)
- // Convert using Inkscape
- args := []string{
- "--export-type=" + string(options.Format),
- "--export-width=" + fmt.Sprintf("%d", options.Width),
- "--export-height=" + fmt.Sprintf("%d", options.Height),
- "--export-dpi=" + fmt.Sprintf("%d", options.DPI),
- "--export-filename=" + outputPath,
- tempSVG,
- }
- cmd := exec.Command("inkscape", args...)
- return cmd.Run()
- }
- // convertWithImageMagick uses ImageMagick for conversion
- func (e *AdvancedExporter) convertWithImageMagick(svgContent, outputPath string, options *ExportOptions) error {
- if !e.isCommandAvailable("convert") {
- return fmt.Errorf("imagemagick not available")
- }
- tempSVG := outputPath + ".temp.svg"
- if err := writeFile(tempSVG, []byte(svgContent)); err != nil {
- return err
- }
- defer removeFile(tempSVG)
- args := []string{
- "-density", fmt.Sprintf("%d", options.DPI),
- "-resize", fmt.Sprintf("%dx%d", options.Width, options.Height),
- tempSVG,
- outputPath,
- }
- cmd := exec.Command("convert", args...)
- return cmd.Run()
- }
- // convertWithRSVG uses rsvg-convert for conversion
- func (e *AdvancedExporter) convertWithRSVG(svgContent, outputPath string, options *ExportOptions) error {
- if !e.isCommandAvailable("rsvg-convert") {
- return fmt.Errorf("rsvg-convert not available")
- }
- tempSVG := outputPath + ".temp.svg"
- if err := writeFile(tempSVG, []byte(svgContent)); err != nil {
- return err
- }
- defer removeFile(tempSVG)
- format := "png"
- args := []string{
- "--format=" + format,
- "--width=" + fmt.Sprintf("%d", options.Width),
- "--height=" + fmt.Sprintf("%d", options.Height),
- "--dpi-x=" + fmt.Sprintf("%d", options.DPI),
- "--dpi-y=" + fmt.Sprintf("%d", options.DPI),
- "--output=" + outputPath,
- tempSVG,
- }
- cmd := exec.Command("rsvg-convert", args...)
- return cmd.Run()
- }
- // convertWithChrome uses headless Chrome for conversion
- func (e *AdvancedExporter) convertWithChrome(svgContent, outputPath string, options *ExportOptions) error {
- chromePath := e.findChrome()
- if chromePath == "" {
- return fmt.Errorf("chrome not available")
- }
- // Create HTML wrapper
- html := fmt.Sprintf(`<!DOCTYPE html>
- <html>
- <head>
- <style>
- body { margin: 0; padding: 0; }
- svg { width: %dpx; height: %dpx; }
- </style>
- </head>
- <body>
- %s
- </body>
- </html>`, options.Width, options.Height, svgContent)
- tempHTML := outputPath + ".temp.html"
- if err := writeFile(tempHTML, []byte(html)); err != nil {
- return err
- }
- defer removeFile(tempHTML)
- args := []string{
- "--headless",
- "--disable-gpu",
- "--no-sandbox",
- "--window-size=" + fmt.Sprintf("%d,%d", options.Width, options.Height),
- "--screenshot=" + outputPath,
- "file://" + tempHTML,
- }
- cmd := exec.Command(chromePath, args...)
- return cmd.Run()
- }
- // isCommandAvailable checks if a command is available in PATH
- func (e *AdvancedExporter) isCommandAvailable(command string) bool {
- _, err := exec.LookPath(command)
- return err == nil
- }
- // findChrome finds Chrome/Chromium executable
- func (e *AdvancedExporter) findChrome() string {
- candidates := []string{
- "google-chrome",
- "google-chrome-stable",
- "chromium",
- "chromium-browser",
- }
- if runtime.GOOS == "darwin" {
- candidates = append(candidates, "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome")
- } else if runtime.GOOS == "windows" {
- candidates = append(candidates,
- "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
- "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
- )
- }
- for _, candidate := range candidates {
- if path, err := exec.LookPath(candidate); err == nil {
- return path
- }
- }
- return ""
- }
- // GetAvailableConverters returns list of available conversion tools
- func (e *AdvancedExporter) GetAvailableConverters() []string {
- var available []string
- tools := map[string]string{
- "Inkscape": "inkscape",
- "ImageMagick": "convert",
- "rsvg-convert": "rsvg-convert",
- "Chrome": "",
- }
- for name, command := range tools {
- if name == "Chrome" {
- if e.findChrome() != "" {
- available = append(available, name)
- }
- } else if e.isCommandAvailable(command) {
- available = append(available, name)
- }
- }
- return available
- }
- // Helper functions
- func writeFile(path string, data []byte) error {
- // Implementation would write file
- return nil
- }
- func removeFile(path string) {
- // Implementation would remove file
- }
- // InstallationGuide provides installation instructions for external tools
- func (e *AdvancedExporter) InstallationGuide() map[string]string {
- return map[string]string{
- "Inkscape": `
- macOS: brew install inkscape
- Ubuntu/Debian: sudo apt-get install inkscape
- Windows: Download from https://inkscape.org/`,
- "ImageMagick": `
- macOS: brew install imagemagick
- Ubuntu/Debian: sudo apt-get install imagemagick
- Windows: Download from https://imagemagick.org/`,
- "rsvg-convert": `
- macOS: brew install librsvg
- Ubuntu/Debian: sudo apt-get install librsvg2-bin
- Windows: Part of GTK+ runtime`,
- "Chrome": `
- macOS: Download from https://www.google.com/chrome/
- Ubuntu/Debian: sudo apt-get install google-chrome-stable
- Windows: Download from https://www.google.com/chrome/`,
- }
- }
|