advanced.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // Package exporter provides advanced export functionality
  2. package exporter
  3. import (
  4. "fmt"
  5. "os/exec"
  6. "runtime"
  7. "mermaid-go/pkg/ast"
  8. )
  9. // AdvancedExporter provides high-quality export using external tools
  10. type AdvancedExporter struct {
  11. svgExporter *SVGExporter
  12. }
  13. // NewAdvancedExporter creates a new advanced exporter
  14. func NewAdvancedExporter() *AdvancedExporter {
  15. return &AdvancedExporter{
  16. svgExporter: NewSVGExporter(),
  17. }
  18. }
  19. // ExportWithExternalTool exports using external tools for better quality
  20. func (e *AdvancedExporter) ExportWithExternalTool(diagram ast.Diagram, outputPath string, options *ExportOptions) error {
  21. if options == nil {
  22. options = DefaultExportOptions()
  23. }
  24. // First export to SVG
  25. e.svgExporter.SetSize(options.Width, options.Height)
  26. svgContent, err := e.svgExporter.ExportToSVG(diagram)
  27. if err != nil {
  28. return fmt.Errorf("failed to generate SVG: %w", err)
  29. }
  30. // If target is SVG, we're done
  31. if options.Format == FormatSVG {
  32. return writeFile(outputPath, []byte(svgContent))
  33. }
  34. // For PNG/JPEG, try to use external tools
  35. return e.convertSVGToRaster(svgContent, outputPath, options)
  36. }
  37. // convertSVGToRaster converts SVG to raster format using external tools
  38. func (e *AdvancedExporter) convertSVGToRaster(svgContent, outputPath string, options *ExportOptions) error {
  39. // Try different conversion methods in order of preference
  40. converters := []func(string, string, *ExportOptions) error{
  41. e.convertWithInkscape,
  42. e.convertWithImageMagick,
  43. e.convertWithRSVG,
  44. e.convertWithChrome,
  45. }
  46. var lastErr error
  47. for _, converter := range converters {
  48. if err := converter(svgContent, outputPath, options); err == nil {
  49. return nil
  50. } else {
  51. lastErr = err
  52. }
  53. }
  54. return fmt.Errorf("all conversion methods failed, last error: %w", lastErr)
  55. }
  56. // convertWithInkscape uses Inkscape for conversion
  57. func (e *AdvancedExporter) convertWithInkscape(svgContent, outputPath string, options *ExportOptions) error {
  58. if !e.isCommandAvailable("inkscape") {
  59. return fmt.Errorf("inkscape not available")
  60. }
  61. // Create temporary SVG file
  62. tempSVG := outputPath + ".temp.svg"
  63. if err := writeFile(tempSVG, []byte(svgContent)); err != nil {
  64. return err
  65. }
  66. defer removeFile(tempSVG)
  67. // Convert using Inkscape
  68. args := []string{
  69. "--export-type=" + string(options.Format),
  70. "--export-width=" + fmt.Sprintf("%d", options.Width),
  71. "--export-height=" + fmt.Sprintf("%d", options.Height),
  72. "--export-dpi=" + fmt.Sprintf("%d", options.DPI),
  73. "--export-filename=" + outputPath,
  74. tempSVG,
  75. }
  76. cmd := exec.Command("inkscape", args...)
  77. return cmd.Run()
  78. }
  79. // convertWithImageMagick uses ImageMagick for conversion
  80. func (e *AdvancedExporter) convertWithImageMagick(svgContent, outputPath string, options *ExportOptions) error {
  81. if !e.isCommandAvailable("convert") {
  82. return fmt.Errorf("imagemagick not available")
  83. }
  84. tempSVG := outputPath + ".temp.svg"
  85. if err := writeFile(tempSVG, []byte(svgContent)); err != nil {
  86. return err
  87. }
  88. defer removeFile(tempSVG)
  89. args := []string{
  90. "-density", fmt.Sprintf("%d", options.DPI),
  91. "-resize", fmt.Sprintf("%dx%d", options.Width, options.Height),
  92. tempSVG,
  93. outputPath,
  94. }
  95. cmd := exec.Command("convert", args...)
  96. return cmd.Run()
  97. }
  98. // convertWithRSVG uses rsvg-convert for conversion
  99. func (e *AdvancedExporter) convertWithRSVG(svgContent, outputPath string, options *ExportOptions) error {
  100. if !e.isCommandAvailable("rsvg-convert") {
  101. return fmt.Errorf("rsvg-convert not available")
  102. }
  103. tempSVG := outputPath + ".temp.svg"
  104. if err := writeFile(tempSVG, []byte(svgContent)); err != nil {
  105. return err
  106. }
  107. defer removeFile(tempSVG)
  108. format := "png"
  109. args := []string{
  110. "--format=" + format,
  111. "--width=" + fmt.Sprintf("%d", options.Width),
  112. "--height=" + fmt.Sprintf("%d", options.Height),
  113. "--dpi-x=" + fmt.Sprintf("%d", options.DPI),
  114. "--dpi-y=" + fmt.Sprintf("%d", options.DPI),
  115. "--output=" + outputPath,
  116. tempSVG,
  117. }
  118. cmd := exec.Command("rsvg-convert", args...)
  119. return cmd.Run()
  120. }
  121. // convertWithChrome uses headless Chrome for conversion
  122. func (e *AdvancedExporter) convertWithChrome(svgContent, outputPath string, options *ExportOptions) error {
  123. chromePath := e.findChrome()
  124. if chromePath == "" {
  125. return fmt.Errorf("chrome not available")
  126. }
  127. // Create HTML wrapper
  128. html := fmt.Sprintf(`<!DOCTYPE html>
  129. <html>
  130. <head>
  131. <style>
  132. body { margin: 0; padding: 0; }
  133. svg { width: %dpx; height: %dpx; }
  134. </style>
  135. </head>
  136. <body>
  137. %s
  138. </body>
  139. </html>`, options.Width, options.Height, svgContent)
  140. tempHTML := outputPath + ".temp.html"
  141. if err := writeFile(tempHTML, []byte(html)); err != nil {
  142. return err
  143. }
  144. defer removeFile(tempHTML)
  145. args := []string{
  146. "--headless",
  147. "--disable-gpu",
  148. "--no-sandbox",
  149. "--window-size=" + fmt.Sprintf("%d,%d", options.Width, options.Height),
  150. "--screenshot=" + outputPath,
  151. "file://" + tempHTML,
  152. }
  153. cmd := exec.Command(chromePath, args...)
  154. return cmd.Run()
  155. }
  156. // isCommandAvailable checks if a command is available in PATH
  157. func (e *AdvancedExporter) isCommandAvailable(command string) bool {
  158. _, err := exec.LookPath(command)
  159. return err == nil
  160. }
  161. // findChrome finds Chrome/Chromium executable
  162. func (e *AdvancedExporter) findChrome() string {
  163. candidates := []string{
  164. "google-chrome",
  165. "google-chrome-stable",
  166. "chromium",
  167. "chromium-browser",
  168. }
  169. if runtime.GOOS == "darwin" {
  170. candidates = append(candidates, "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome")
  171. } else if runtime.GOOS == "windows" {
  172. candidates = append(candidates,
  173. "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
  174. "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
  175. )
  176. }
  177. for _, candidate := range candidates {
  178. if path, err := exec.LookPath(candidate); err == nil {
  179. return path
  180. }
  181. }
  182. return ""
  183. }
  184. // GetAvailableConverters returns list of available conversion tools
  185. func (e *AdvancedExporter) GetAvailableConverters() []string {
  186. var available []string
  187. tools := map[string]string{
  188. "Inkscape": "inkscape",
  189. "ImageMagick": "convert",
  190. "rsvg-convert": "rsvg-convert",
  191. "Chrome": "",
  192. }
  193. for name, command := range tools {
  194. if name == "Chrome" {
  195. if e.findChrome() != "" {
  196. available = append(available, name)
  197. }
  198. } else if e.isCommandAvailable(command) {
  199. available = append(available, name)
  200. }
  201. }
  202. return available
  203. }
  204. // Helper functions
  205. func writeFile(path string, data []byte) error {
  206. // Implementation would write file
  207. return nil
  208. }
  209. func removeFile(path string) {
  210. // Implementation would remove file
  211. }
  212. // InstallationGuide provides installation instructions for external tools
  213. func (e *AdvancedExporter) InstallationGuide() map[string]string {
  214. return map[string]string{
  215. "Inkscape": `
  216. macOS: brew install inkscape
  217. Ubuntu/Debian: sudo apt-get install inkscape
  218. Windows: Download from https://inkscape.org/`,
  219. "ImageMagick": `
  220. macOS: brew install imagemagick
  221. Ubuntu/Debian: sudo apt-get install imagemagick
  222. Windows: Download from https://imagemagick.org/`,
  223. "rsvg-convert": `
  224. macOS: brew install librsvg
  225. Ubuntu/Debian: sudo apt-get install librsvg2-bin
  226. Windows: Part of GTK+ runtime`,
  227. "Chrome": `
  228. macOS: Download from https://www.google.com/chrome/
  229. Ubuntu/Debian: sudo apt-get install google-chrome-stable
  230. Windows: Download from https://www.google.com/chrome/`,
  231. }
  232. }