Skip to content

Figure.pygmtlogo: Improved positioning of horizontal wordmark for circular PyGMT logo#4627

Open
seisman wants to merge 10 commits into
pygmtlogofrom
pygmtlogo-font
Open

Figure.pygmtlogo: Improved positioning of horizontal wordmark for circular PyGMT logo#4627
seisman wants to merge 10 commits into
pygmtlogofrom
pygmtlogo-font

Conversation

@seisman
Copy link
Copy Markdown
Member

@seisman seisman commented May 7, 2026

The "Space Grotesk" font used in the original PyGMT logo design (#1404 (comment)) looks great. However, this font is not available in GMT. If we want to use it, there are two possible approaches:

  1. Configure PyGMT to download and use the font dynamically, which would require an internet connection.
  2. Convert the glyphs of the font into polygons and render them using Figure.plot.

Option 2 is technically feasible, and the polygons could potentially be generated using AI tools such as ChatGPT. However, the resulting polygon data would likely be extremely complex and verbose.

Given these limitations, I think we will probably need to rely on the built-in GMT fonts instead. Currently, we use font 13 (AvantGarde-Book). I temporarily modified the source code to allow the wordmark font to be set dynamically and tested all available GMT fonts.

import pygmt

fig = pygmt.Figure()
with fig.subplot(nrows=5, ncols=8, subsize=("2.5c", "3c"), tag=0):
    for font in range(0, 34):
        with fig.set_panel(panel=font):
            fig.pygmtlogo(font=font, wordmark="vertical")
fig.show()
fig.savefig("logos-vertical.png")

fig = pygmt.Figure()
with fig.subplot(nrows=9, ncols=4, subsize=("9c", "2.5c"), tag=0):
    for font in range(0, 34):
        with fig.set_panel(panel=font):
            fig.pygmtlogo(font=font, wordmark="horizontal")
fig.show()
fig.savefig("logos-horizontal.png")

GMT Fonts: https://docs.generic-mapping-tools.org/dev/reference/postscript-fonts.html

Horizontal (the subplot tag is the font ID)

logos-horizontal

Vertical (the subplot tag is the font ID)

logos-vertical

@yvonnefroehlich
Copy link
Copy Markdown
Member

I also really like the font Space Grotesk by Florian Karsten, but I think it's much easier to use a font supported by GMT.
Also not sure about the copyright for this font (https://fonts.google.com/specimen/Space+Grotesk/license?preview.script=Latn).

@seisman
Copy link
Copy Markdown
Member Author

seisman commented May 8, 2026

The 34 GMT built-in fonts can be broadly divided into four groups:

  • Serif fonts: Times (4–7), Bookman (17–20), NewCenturySchlbk (25–28), and Palatino (29–32)
  • Sans-serif fonts: Helvetica (0–3), AvantGarde (13–16), and Helvetica-Narrow (21–24)
  • Monospace fonts: Courier (8–11)
  • Special fonts: Symbol (12) and ZapfChancery-MediumItalic (33)

Here are my thoughts on the font choices:

  • Monospace fonts are generally awkward for branding because the fixed-width spacing gives a typewriter or terminal-like appearance.
  • Serif fonts are more commonly associated with publication typography, books, and long-form reading rather than modern software branding.
  • Sans-serif fonts usually appear cleaner and more modern, and many software projects/packages (e.g., Python, xarray, seaborn, and pandas) also use sans-serif fonts in their logos or branding.
  • In the AvantGarde font family, I feel that the letter T is too thin compared to the other letters, making the wordmark appear slightly imbalanced.
  • For Helvetica or Helvetica-Narrow, the bold variants make the wordmark appear too heavy relative to the circular logo, while the italic variants look a little unnatural to me.

This leaves Helvetica (0) and Helvetica-Narrow (21) as the remaining candidates.

For the horizontal version, I do not particularly like the regular Helvetica font because the wordmark appears much wider than the circular logo (the width ratio is about 3.4:1), making the text visually dominant. In comparison, Helvetica-Narrow produces a more balanced composition between the icon and the wordmark (the width ratio is 2.7:1).

For the vertical version, however, the situation is reversed. Since the wordmark is placed below the logo, horizontal compactness becomes less important. In this layout, Helvetica-Narrow appears slightly too narrow relative to the circular logo, whereas the regular Helvetica font provides a more balanced overall appearance.

Of course, the balance could also be improved by adjusting the text size. Another possible solution is to use Helvetica-Narrow for the horizontal version and regular Helvetica for the vertical version. Since the two fonts belong to the same font family and are visually very similar, this approach would still maintain a consistent overall branding style while achieving better balance in each layout.

Here is a comparison of Helvetica, Helvetica-Narrow, and AvantGarde-Book:

import pygmt

fig = pygmt.Figure()
with fig.subplot(nrows=3, ncols=2, subsize=("10c,3c", "3.5c"), tag=True): 
    for font in ("Helvetica", "Helvetica-Narrow", "AvantGarde-Book"):
        for orientation in ("horizontal", "vertical"):
            with fig.set_panel(tag=font):
                fig.pygmtlogo(font=font, wordmark=orientation)
fig.show()
fig.savefig("logos.png")
logos

@seisman seisman added the enhancement Improving an existing feature label May 9, 2026
@seisman seisman added this to the 0.19.0 milestone May 9, 2026
@seisman seisman added the needs review This PR has higher priority and needs review. label May 9, 2026
@yvonnefroehlich
Copy link
Copy Markdown
Member

Wow, this is quite a detailed study on fonts!

My argument for choosing the font "AvantGarde-Book" was that the letter "G" in the visual looks similar to the "G" used by the font (circle shape, no vertical line at the right bottom).

pygmt_logo_letter_G

Besides the font, I wondering about the font color, especially whether it is more consistent to write "GMT" in red (light and dark themes) for the color version instead of in darkgray (light theme) or white (dark theme):

pygmt_logo_font_color

@seisman
Copy link
Copy Markdown
Member Author

seisman commented May 12, 2026

My argument for choosing the font "AvantGarde-Book" was that the letter "G" in the visual looks similar to the "G" used by the font (circle shape, no vertical line at the right bottom).

That's a reasonable argument. Looking at the logos above, I now feel that the wordmark in "Helvetica-Narrow" is a little too condensed. The Helvetica font is also very commonly used, so I feel we should use "AvantGarde-Book" to give the wordmark a more distinctive identity.

This reverts commit 88f381a.
@seisman
Copy link
Copy Markdown
Member Author

seisman commented May 13, 2026

I'm experimenting with positioning the wordmark more accurately. Here are some interesting findings.

I initially tried using x=4, y=0, and justify="ML", expecting the left edge of the letter P to align exactly with the x=4.0 gridline. However, that is not what happens. The letter P includes extra space to the left of the visible glyph, and the letter y extends below y=-4.0. Along the way, I learned some typography concepts such as side bearings and descender depth (xref: https://freetype.org/freetype2/docs/glyphs/glyphs-3.html, https://en.wikipedia.org/wiki/Descender).

import pygmt
from pygmt.params import Position
fig = pygmt.Figure()
fig.basemap(region=[-4, 32, -4, 4], projection="x1c", frame="ag1")
fig.pygmtlogo(width="8c", position=Position((0, 0), cstype="mapcoords", anchor="MC"))
fig.text(x=4, y=0, text="PyGMT", justify="ML", font="8c,AvantGarde-Book", no_clip=True)
fig.show()
fig.savefig("logo-wordmark.png")
logo-wordmark

So, to position the wordmark accurately, we need to know the font metrics for the individual letters. With the help of ChatGPT, I found the detailed AFM definition for the AvantGarde-Book font. On Linux, the file can typically be found at /usr/share/fonts/urw-base35/URWGothic-Book.afm
(note that modern Ghostscript replaces AvantGarde-Book with URWGothic-Book).

The relevant metrics are:

C 80 ; WX 592 ; N P ; B 76 0 565 739 ;
C 121 ; WX 536 ; N y ; B 9 -192 527 547 ;
C 71 ; WX 872 ; N G ; B 44 -13 831 752 ;
C 77 ; WX 919 ; N M ; B 76 0 843 739 ;
C 84 ; WX 426 ; N T ; B 7 0 419 739 ;

ChatGPT helped me understand these numbers. Taking the first record as an example:

C 80 ; WX 592 ; N P ; B 76 0 565 739 ;
  • C 80: character code 80, i.e., the ASCII code for P
  • WX 592: advance width = 592 font units
  • N P: glyph name = P
  • B 76 0 565 739: bounding box in the format llx lly urx ury (left, bottom, right, top)

These values are given in units of 1/1000 of the font size. For example, with a font size of 8c, the left side bearing of P is 76 / 1000 × 8c = 0.608c.

So, what we can derive from these metrics are:

  • The left side bearing (LSB) of the letter P is 0.076
  • The descender depth of the letter y is 0.192
  • The heights are 0.739 for the letters PMT, and 0.765 for the letter G
  • The full visible width of PyGMT is ((592 + 536 + 872 + 919 + 426) - 76 - 7) / 1000 = 3.262, i.e., the total advance widths minus the left side bearing of P and the right side bearing of T

Here is a script to visualize the metrics in detail. Some notes about the script:

  • The visual part of the logo is not included for simplicity
  • "PyGMT" is placed at (0, 0) with justify="BL"
  • The font size is set to 8c
  • The variables pheight, ydesc, plsb, pygmtwidth are calculated from the font metrics
  • The variable pstroke is the stroke thickness of the letter P. ChatGPT told me that the AFM file does not store this value, so it is set empirically to 0.0735
import pygmt
fig = pygmt.Figure()
fig.basemap(region=[-4, 32, -4, 10], projection="x1c", frame="afg0.5")

x0, y0 = 0, 0
fontsize = 8

pheight = 0.739  # Height of "P"
plsb = 0.076 # Left side bearing of "P"
pstroke = 0.0735  # Stroke thickness of "P"
ydesc = 0.192  # Descender depth of "y"
pygmtwidth = ((592 + 536 + 872 + 919 + 426) - 76 - 7) / 1000   # Full width of "PyGMT"

# Plot the coordinates we set 
fig.plot(x=x0, y=y0, style="c0.5c", fill="red")
# Plot three hlines: baseline, top of P, and bottom of y.
fig.hlines(y=[y0, y0 + fontsize * pheight, y0 - fontsize * ydesc], pen="3p,red@50")
# Plot three vlines: left edge of P, left edge of P + stroke thickness, and right edge of T
fig.vlines(x=[x0 + fontsize * plsb, x0 + fontsize * (plsb + pstroke), x0 + fontsize * plsb + fontsize * pygmtwidth], pen="3p,red@50")
# The actual text
fig.text(x=x0, y=y0, text="PyGMT", justify="BL", font=f"{fontsize}c,AvantGarde-Book")
fig.show(width=1000)
fig.savefig("wordmark.png")
wordmark

Since the stroke thickness is 0.0735 × fontsize, if we want the stroke thickness to match the thickness of the logo outline, then the optimal font size is: (128 - 112) / 128 * 4 / 0.0735 = 6.8c

As for the final logo positioning, we can either:

  • Align the bottom of the letter y exactly to y=-4.0, or
  • Vertically center the letters PGMT while ignoring the descender of y
import pygmt
from pygmt.params import Position

size = 4
height = 0.739
ybottom = 0.192
pleft = 0.076
pthick = 0.0735

fontsize = (128 - 112) / 128 * size / pthick

# version 1: Align the bottom of the letter y exactly to y=-4.0
x0, y0, justify = 1.25 * size - fontsize * pleft, -size + fontsize * ybottom, "BL"
# version 2: Vertically center the letters PGMT (ignoring the descender of y), using "ML"
x0, y0, justify = 1.25 * size - fontsize * pleft, 0, "ML"
# version 3: Vertically center the letters PGMT, accurately
x0, y0, justify = 1.25 * size - fontsize * pleft, - fontsize * height / 2.0, "BL"

fig = pygmt.Figure()
fig.basemap(region=[-4, 32, -4, 4], projection="x1c", frame="ag1")
fig.pygmtlogo(width="8c", position=Position((0, 0), cstype="mapcoords", anchor="MC"))
fig.text(x=x0, y=y0, justify=justify, text="PyGMT", font=f"{fontsize}c,AvantGarde-Book", no_clip=True)
fig.show()
fig.savefig("logo-wordmark.png")
Version 1 Version 2 Version 3
logo-wordmark logo-wordmark logo-wordmark

Personally I like version 2, due to its symmetry in the Y direction and the y coordinate can be simply set to y=0.

Edit: Actually, even in version 2, the letter P is still not vertically centered. It's shifted downward a little bit.
Edit: I've added version 3, which is more accurately centered vertically.

@seisman seisman changed the title POC + WIP: What's the best font for the workmark? Figure.pygmtlogo: Final design of the logo with a horizontal wordmark May 13, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

Summary of changed images

This is an auto-generated report of images that have changed on the DVC remote

Status Path
added pygmt/tests/baseline/test_pygmtlogo_circle_horizontal_wordmark.png
modified pygmt/tests/baseline/test_pygmtlogo_circle_design_horizontal.png

Image diff(s)

Details

Added images

  • test_pygmtlogo_circle_horizontal_wordmark.png

Modified images

Path Old New
test_pygmtlogo_circle_design_horizontal.png

Report last updated at commit be6c052

@seisman
Copy link
Copy Markdown
Member Author

seisman commented May 13, 2026

I've adopted version 3 and updated the code so that:

  • The left edge of the letter P aligns exactly at x=5.0
  • The letters PGMT are vertically centered, while the descender of y is ignored
  • The font size is chosen such that the stroke thickness of the letter P matches the outline thickness of the logo shape. The calculated font size is 6.8c. If this appears too small visually, we could further scale it by an empirical factor.

@seisman
Copy link
Copy Markdown
Member Author

seisman commented May 13, 2026

Besides the font, I wondering about the font color, especially whether it is more consistent to write "GMT" in red (light and dark themes) for the color version instead of in darkgray (light theme) or white (dark theme):

Let's focus on the positioning of horizontal wordmarks in this PR and discuss the coloring separately.

@seisman seisman marked this pull request as ready for review May 13, 2026 06:26
@seisman seisman changed the title Figure.pygmtlogo: Final design of the logo with a horizontal wordmark Figure.pygmtlogo: Improved positioning of horizontal wordmark for circular PyGMT logo May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improving an existing feature needs review This PR has higher priority and needs review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants