Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image formatter #245

Closed
divan opened this issue Apr 15, 2019 · 12 comments
Closed

Image formatter #245

divan opened this issue Apr 15, 2019 · 12 comments

Comments

@divan
Copy link

divan commented Apr 15, 2019

I'd love to use Chroma for generating screenshots of the code – from short carbon.sh-like ones to the long whole files screenshots.
Turns out, making long code screenshot it's not a trivial task and most tools solve it by scrolling window and stitching it together. I believe chroma's concept of Formatters is perfect for implementing this functionality natively.

I might be willing to work on this in my spare time, but I want to check that I'm not duplicating work and not missing something crucial (perhaps it's in progress already, or there some limitations I'm not aware of).

Thanks in advance for thoughts on this.

@alecthomas
Copy link
Owner

I think that would be great. FWIW, Pygments has image formatters.

A PDF formatter would also be amazing, but I suspect that would be quite a bit more work.

@alecthomas
Copy link
Owner

SVG would be awesome too.. and maybe that would be a) easier and b) fulfil the image requirements you need?

@divan
Copy link
Author

divan commented Apr 15, 2019

Thanks!
Hmm, that's interesting.

PDF

I've been exploring Go-native PDF writers, and seems like @jung-kurt's https://github.com/jung-kurt/gofpdf is the most battle-tested solution (yet it's a port of PHP's FPDF library, so design is not idiomatic and it lacks some features like proper Unicode support in PDF). But I use it in production for years, and it works great, I might try to implement PDF Formatter based on it.

There is also fantastic in terms of code quality and PDF spec coverage https://github.com/unidoc/unidoc library, but it has very unusual licensing, which stops me from using it everywhere.

SVG

I'm not sure I know how well SVG handles font rendering, but I know @ajstarks hack SVG things for breakfast, so maybe his input will be valuable here.

Images

For images (I need PNG output for my case), I think the main challenge will be handling fonts and layout. I'll explore it to understand a scope of work.

@divan
Copy link
Author

divan commented Apr 15, 2019

Quick implementation using github.com/fogleman/gg looks good and actually covers my case:

Current hacky code

formatters/image.go

package formatters

import (
	"fmt"
	"io"
	"strings"

	"github.com/alecthomas/chroma"
	"github.com/fogleman/gg"
)

// Image formatter outputs the formatted text to the image.
var Image = Register("image", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, it chroma.Iterator) error {
	const S = 1280

	dc := gg.NewContext(S, S*3)
	dc.SetRGB(color(s.Get(chroma.Background).Background))
	dc.Clear()
	dc.SetRGB(1, 1, 1)
	if err := dc.LoadFontFace("/tmp/Hack-Regular.ttf", 22); err != nil {
		panic(err)
	}

	tokens := it.Tokens()
	lines := chroma.SplitTokensIntoLines(tokens)
	_, wh := dc.MeasureString("line")
	var x, y float64
	for _, line := range lines {
		x = 0
		y += wh * 1.5
		for _, t := range line {
			value := t.Value

			se := s.Get(t.Type)
			fg := se.Colour
			dc.SetRGB(color(fg))
			value = strings.Replace(t.Value, "\t", "      ", -1)

			dc.DrawStringWrapped(value, x, y, 0.0, 0.0, S, 1.5, gg.AlignLeft)

			ww, _ := dc.MeasureString(value)
			x += ww
		}
	}
	dc.Clip()
	dc.SavePNG("out.png")
	fmt.Println("Written output to out.png")

	return nil
}))

func color(c chroma.Colour) (r, g, b float64) {
	return float64(c.Red()) / 255, float64(c.Green()) / 255, float64(c.Blue()) / 255
}

diff ./cmd/chroma/main.go

diff --git a/cmd/chroma/main.go b/cmd/chroma/main.go
index b57fc3a..95796e7 100644
--- a/cmd/chroma/main.go
+++ b/cmd/chroma/main.go
@@ -50,6 +50,8 @@ command, for Go.

                JSON bool `help:"Output JSON representation of tokens."`

+               Image bool `help:"Output result as an image"`
+
                HTML                      bool   `help:"Enable HTML mode (equivalent to '--formatter html')."`
                HTMLPrefix                string `help:"HTML CSS class prefix." placeholder:"PREFIX"`
                HTMLStyles                bool   `help:"Output HTML CSS styles."`
@@ -120,6 +122,10 @@ func main() {
                cli.Formatter = "html"
        }

+       if cli.Image {
+               cli.Formatter = "image"
+       }
+
        // Retrieve user-specified style, clone it, and add some overrides.
        builder := styles.Get(cli.Style).Builder()
        if cli.HTMLHighlightStyle != "" {

My main question is how to provide options for:

  • font selection
  • image type (jpg/png/etc)
  • image width

Should it go to command line arguments, like --html- options?

Also, some options like HTMLLines or HTMLHighlight can be applied to the image (and others) formatter, so perhaps there is a room for improving command line API?

Example:
chroma -s monokai -f image /usr/local/go/src/container/heap/heap.go:
out

@alecthomas
Copy link
Owner

This is awesome.

My main question is how to provide options for: font selection, image type (jpg/png/etc), image width

Use the same approach as the HTML formatter; function options to configure it, and map those to a set of flags with --image- prefix, with reasonable defaults.

@alecthomas
Copy link
Owner

I guess my only slight concern is that it pulls in a fairly large set of dependencies, but the functionality is super nice so I'm okay with that.

Also, some options like HTMLLines or HTMLHighlight can be applied to the image (and others) formatter, so perhaps there is a room for improving command line API?

Yes, good idea. Something like --lines and --highlight should be fine, though it will need to still support the old flags for backwards compatibility.

@alecthomas
Copy link
Owner

Any progress on this?

@divan
Copy link
Author

divan commented Sep 26, 2019

Nope, sorry, I used it just once.
I might try to find some time to work on it, but can't afford to embark into yet another project to support at the moment

@rsteube
Copy link
Contributor

rsteube commented Sep 27, 2019

Had a look at pygmentize -f svg and it looks pretty similar to the html formatter so i played around a little: https://github.com/rsteube/chroma-svg

test

@alecthomas
Copy link
Owner

Nice! Does it handle text wrapping? Send a PR and we'll merge it in.

@rsteube
Copy link
Contributor

rsteube commented Sep 28, 2019

No (https://www.oreilly.com/library/view/svg-text-layout/9781491933817/ch04.html), but should be possible to add a maxwidth (characters) options and then split the lines programatically in the formatter.
Currently doing some cleanup and will then open a PR with an initial version.

@alecthomas
Copy link
Owner

So we now have SVG support thanks to @rsteube, I think this can be closed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants