package converter import ( "context" "os" "path/filepath" "runtime" "time" "github.com/chromedp/cdproto/page" "github.com/chromedp/chromedp" ) type Converter struct { chromePath string } func NewConverter(chromePath string) *Converter { return &Converter{chromePath: chromePath} } func (c *Converter) Convert(imputFile, outputFile string) error { chromePath := c.getChromePath() htmlURL := "file://" switch runtime.GOOS { case "windows": htmlURL += "/" } // Convert to absolute path absPath, err := filepath.Abs(imputFile) if err != nil { return err } htmlURL += filepath.ToSlash(absPath) opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.ExecPath(chromePath), chromedp.NoSandbox, chromedp.Headless, chromedp.DisableGPU, ) allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) defer cancel() ctx, cancel := chromedp.NewContext(allocCtx) defer cancel() ctx, cancel = context.WithTimeout(ctx, 60*time.Second) defer cancel() var pdf []byte err = chromedp.Run(ctx, chromedp.Navigate(htmlURL), chromedp.WaitReady("body", chromedp.ByQuery), chromedp.ActionFunc(func(ctx context.Context) error { buf, _, err := page.PrintToPDF(). WithPrintBackground(true). WithPaperWidth(8.27). WithPaperHeight(11.69). Do(ctx) if err != nil { return err } pdf = buf return nil }), ) if err != nil { return err } // Save PDF to file if err := os.WriteFile(outputFile, pdf, 0644); err != nil { return err } return nil } // getChromePath checks for system Chrome, else falls back to bundled headless shell func (c *Converter) getChromePath() string { // Candidate paths for system Chrome candidates := []string{} switch runtime.GOOS { case "windows": candidates = []string{ `C:\Program Files\Google\Chrome\Application\chrome.exe`, `C:\Program Files (x86)\Google\Chrome\Application\chrome.exe`, `C:\Program Files\Chromium\Application\chrome.exe`, } case "darwin": candidates = []string{ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium", } default: // Linux candidates = []string{ "/usr/bin/google-chrome", "/usr/bin/chromium-browser", "/usr/bin/chromium", } } // Check system paths first for _, path := range candidates { if _, err := os.Stat(path); err == nil { return path } } // Fallback: use bundled headless shell chromeExec := "chrome-headless-shell" if runtime.GOOS == "windows" { chromeExec += ".exe" } return filepath.Join(c.chromePath, chromeExec) }