Skip to content

Commit

Permalink
♻️ refactor: replace Monaspace with JetBrains Mono and improve font r…
Browse files Browse the repository at this point in the history
…endering

The commit message is focused on the main change, which is replacing the font and improving rendering. The changes include:
1. Switching from Monaspace to JetBrains Mono
2. Adding better font hinting support
3. Improving monospace character rendering
4. Optimizing token joining for better text rendering
  • Loading branch information
watzon committed Nov 22, 2024
1 parent d617bcb commit 8844a9f
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 38 deletions.
Binary file added pkg/fonts/embedded/JetBrainsMono-Regular.ttf
Binary file not shown.
Binary file removed pkg/fonts/embedded/MonaspaceArgon-Regular.ttf
Binary file not shown.
Binary file removed pkg/fonts/embedded/MonaspaceNeon-Regular.ttf
Binary file not shown.
51 changes: 15 additions & 36 deletions pkg/fonts/fonts.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,6 @@ const (
AxisLigatures = "liga" // Ligatures
)

// Monaspace specific ranges
const (
MonaspaceWeightMin = 200
MonaspaceWeightMax = 800
MonaspaceWidthMin = 100
MonaspaceWidthMax = 125
MonaspaceSlantMin = -11
MonaspaceSlantMax = 1
)

// Font represents a loaded font with its metadata
type Font struct {
Name string // Name of the font family
Expand Down Expand Up @@ -209,33 +199,19 @@ const (
FallbackMono FallbackVariant = "mono"
)

// GetFallback returns either Monaspace Argon or Neon as the fallback font
// GetFallback returns either JetBrainsMono or Inter as the fallback font
func GetFallback(variant FallbackVariant) (*Font, error) {
var filename string
var fontName string
var variations map[string]float32

switch variant {
case FallbackMono:
filename = "MonaspaceNeon-Regular.ttf"
fontName = "Monaspace Neon"
variations = map[string]float32{
AxisWeight: 400, // Regular weight
AxisWidth: 100, // Normal width
AxisSlant: 0, // No slant
AxisTexture: 0, // No texture healing
AxisLigatures: 0, // Disable ligatures
}
filename = "JetBrainsMono-Regular.ttf"
fontName = "JetBrainsMono"
default: // FallbackSans
filename = "MonaspaceArgon-Regular.ttf"
fontName = "Monaspace Argon"
variations = map[string]float32{
AxisWeight: 400, // Regular weight
AxisWidth: 100, // Normal width
AxisSlant: 0, // No slant
AxisTexture: 0, // No texture healing
AxisLigatures: 0, // Disable ligatures
}
filename = "Inter-Regular.ttf"
fontName = "Inter"
}

data, err := embeddedFonts.ReadFile("embedded/" + filename)
Expand Down Expand Up @@ -709,17 +685,20 @@ func (f *Font) GetFontFace(size float64) (font.Face, error) {
return fallback.GetFontFace(size)
}

// Create face options
opts := &opentype.FaceOptions{
Size: size,
DPI: 72,
// Convert to TrueType for better hinting support
ttf, err := f.ToTrueType()
if err != nil {
return nil, fmt.Errorf("failed to convert to TrueType: %v", err)
}

face, err := opentype.NewFace(f.Font, opts)
if err != nil {
return nil, fmt.Errorf("failed to create font face: %v", err)
// Create face options with hinting enabled
opts := &truetype.Options{
Size: size,
DPI: 72,
Hinting: font.HintingFull,
}

face := truetype.NewFace(ttf, opts)
return face, nil
}

Expand Down
36 changes: 34 additions & 2 deletions pkg/syntax/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/font/gofont/gomono"
"golang.org/x/image/math/fixed"
)

// RenderConfig holds configuration for rendering highlighted code to an image
Expand Down Expand Up @@ -536,13 +537,44 @@ func (h *HighlightedCode) RenderToImage(config *RenderConfig) (image.Image, erro
if config.ShowLineNumbers {
x += lineNumberOffset
}

// Check if we're using a monospace font
isMono := isMonospace(config.FontFace)

// Draw each token
for _, token := range line {
c.SetSrc(image.NewUniform(token.Color))
pt := freetype.Pt(x, y)
c.DrawString(token.Text, pt)
x += font.MeasureString(config.FontFace, token.Text).Round()

if isMono {
// For monospace fonts, use fixed character spacing
charWidth := font.MeasureString(config.FontFace, "M").Round()
for _, ch := range token.Text {
c.DrawString(string(ch), pt)
pt.X += fixed.Int26_6(charWidth << 6)
}
x += charWidth * len([]rune(token.Text))
} else {
// For proportional fonts, use natural spacing
c.DrawString(token.Text, pt)
x += font.MeasureString(config.FontFace, token.Text).Round()
}
}
}

return img, nil
}

// isMonospace checks if the font is monospace by sampling character widths
func isMonospace(fontFace font.Face) bool {
isMonospace := true
samples := []rune{'M', 'i', '.', ' ', 'W'}
width := font.MeasureString(fontFace, string(samples[0])).Round()
for _, ch := range samples[1:] {
if font.MeasureString(fontFace, string(ch)).Round() != width {
isMonospace = false
break
}
}
return isMonospace
}
20 changes: 20 additions & 0 deletions pkg/syntax/syntax.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,32 @@ func (f *customFormatter) addToken(text string, tokenType chroma.TokenType, styl
expandedText, newColumn := expandTabs(text, f.currentColumn, f.tabWidth)
f.currentColumn = newColumn

// Check if this token should be joined with the previous token
if len(f.currentLine.Tokens) > 0 && shouldJoinTokens(tokenType) {
lastToken := &f.currentLine.Tokens[len(f.currentLine.Tokens)-1]
// Only join if the colors match
if lastToken.Color == f.createToken(expandedText, tokenType, style).Color {
lastToken.Text += expandedText
return
}
}

// Add the token with expanded text
if expandedText != "" {
f.currentLine.Tokens = append(f.currentLine.Tokens, f.createToken(expandedText, tokenType, style))
}
}

func shouldJoinTokens(tokenType chroma.TokenType) bool {
// Join punctuation tokens
return tokenType == chroma.Punctuation ||
strings.Contains(tokenType.String(), "Punctuation") ||
strings.Contains(tokenType.String(), "Operator") ||
strings.Contains(tokenType.String(), "Parenthesis") ||
strings.Contains(tokenType.String(), "Bracket") ||
strings.Contains(tokenType.String(), "Brace")
}

func (f *customFormatter) processNewlines(text string, tokenType chroma.TokenType, style *chroma.Style) (Line, bool) {
if !strings.Contains(text, "\n") {
return Line{}, false
Expand Down

0 comments on commit 8844a9f

Please sign in to comment.