X Tutup
Skip to content

DOC: Add thumbnail for ftface_props gallery example#31321

Open
AMAN194701 wants to merge 4 commits intomatplotlib:mainfrom
AMAN194701:add-ftface-props-thumbnail
Open

DOC: Add thumbnail for ftface_props gallery example#31321
AMAN194701 wants to merge 4 commits intomatplotlib:mainfrom
AMAN194701:add-ftface-props-thumbnail

Conversation

@AMAN194701
Copy link
Copy Markdown
Contributor

Problem :
The ftface_props example does not generate a gallery thumbnail as it produces no visual output.

Cause :
The example only prints font properties to stdout and does not call plt.savefig() or plt.show(), so Sphinx Gallery cannot auto-generate a thumbnail.

Fix :
Added a custom SVG thumbnail using sphinx_gallery_thumbnail_path. The thumbnail illustrates key font metrics (ascender, cap height, baseline, descender) using the letters "Ag" with annotated reference lines.

Closes #17479

AI Disclosure

I used AI assistance to help generate and iterate on the SVG thumbnail design.

@github-actions github-actions bot added the Documentation: examples files in galleries/examples label Mar 18, 2026
@story645
Copy link
Copy Markdown
Member

story645 commented Mar 18, 2026

I used AI assistance to help generate and iterate on the SVG thumbnail design.

Slightly concerned about this from a licensing point of view. @QuLogic does this seem generic enough? I think the image is descriptive.

ETA: Also thanks the disclosure - it'd be a lot more of a problem if we didn't know AI was used and there was a licensing problem (I don't think that's the case here, but the knowledge helps a lot in the discussion.)

@AMAN194701
Copy link
Copy Markdown
Contributor Author

@story645
Copy link
Copy Markdown
Member

I'd also add this (or an even more detailed pic) to the example - I really like the markup of attributes, it explains what they are really well.

@jklymak
Copy link
Copy Markdown
Member

jklymak commented Mar 18, 2026

It would be even nicer if it agreed with the font displayed. I doubt there are too many fonts where the ascender is so much taller than the height.

@AMAN194701 AMAN194701 force-pushed the add-ftface-props-thumbnail branch from 2f8d1e8 to 3255921 Compare March 18, 2026 09:07
@AMAN194701
Copy link
Copy Markdown
Contributor Author

Thanks for the feedback @jklymak and @story645!
I have Updated the thumbnail.
Here's how it looks:
Screenshot 2026-03-18 at 14 59 04
And here's the gallery page from the CI build: https://output.circle-artifacts.com/output/job/d10adc19-42c2-41f9-b97f-a5964b4df1a1/artifacts/0/doc/build/html/gallery/misc/index.html
Let me know if anything else needs to be changed!

@story645
Copy link
Copy Markdown
Member

Baseline and cap height aren't listed in the font properties - can the labels match the listed properties? And also can you add a box? (I wonder if this might be easier to make in Matplotlib using the font in the example and the coordinate system in font properties)

@AMAN194701
Copy link
Copy Markdown
Contributor Author

Thank you @story645 for the feedback !
I’ll update the labels to match the listed font properties and add a box. I’ll also try generating it using Matplotlib as suggested.

@QuLogic
Copy link
Copy Markdown
Member

QuLogic commented Mar 18, 2026

Maybe it would make sense to change this example to generate that figure as well... But some care might have to be taken to handle fonts that don't have whatever sample characters we end up using.

The image doesn't quite make sense though as I doubt the cap height is going to be the same as the x height, and it certainly isn't for the default DejaVu Sans.

@AMAN194701
Copy link
Copy Markdown
Contributor Author

Thanks ! @QuLogic I ran the example locally and I can see what you mean — Ascender is 1901 and Bbox ymax is 2187, so cap height and x height are clearly not equal for DejaVu Sans, and my static thumbnail was incorrectly showing them as the same.
I also noticed the example currently produces no matplotlib figure at all — it only prints to terminal. Would it make sense to convert this example to actually generate a visual diagram using font.bbox, font.ascender, font.descender, font.underline_position and font.underline_thickness to show these metrics visually? That would make the thumbnail naturally come from the example itself rather than needing a static image.

@QuLogic
Copy link
Copy Markdown
Member

QuLogic commented Mar 20, 2026

Yes, that's what I suggest.

@AMAN194701 AMAN194701 force-pushed the add-ftface-props-thumbnail branch 3 times, most recently from f9343ba to b734475 Compare March 21, 2026 06:01
@AMAN194701
Copy link
Copy Markdown
Contributor Author

Thanks @story645 and @QuLogic for the feedback! I have Updated the example to actually generate a matplotlib figure using font.bbox, font.ascender, font.descender, font.underline_position and font.underline_thickness , all normalised to units_per_EM. The thumbnail is now auto-generated by Sphinx Gallery from the figure itself, no static image needed anymore.

@scottshambaugh
Copy link
Copy Markdown
Contributor

scottshambaugh commented Mar 23, 2026

Please restore the removed comments in the example.

This image also looks incorrect & should not draw lines on top of the text. You will have to manually verify the generated image if using AI tooling - it is not good at verifying technical diagrams
image

Copy link
Copy Markdown
Member

@story645 story645 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I was thinking plot bbox as a box around the image.

print(f"{name:17}", flag in font.face_flags)

# ── Visualise font metrics ──────────────────────────────────────────────────
if font.scalable:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no reason for this if statement in an example b/c it should be known. This can be added to the image as a property if you want to use it.

@AMAN194701
Copy link
Copy Markdown
Contributor Author

Thanks @scottshambaugh and @story645 for the feedback !
i will fix everything and and push update shortly

@AMAN194701
Copy link
Copy Markdown
Contributor Author

Hi @story645 — I just want to confirm the direction before implementing. Is this roughly correct ? I'll use the actual font metrics with the real values from DejaVu Sans Oblique.
Does this sound right, or are there specific properties you'd like included/excluded?
telegram-cloud-photo-size-5-6203946046098968168-y

@story645
Copy link
Copy Markdown
Member

Yeah + a box representing the bbox

@AMAN194701
Copy link
Copy Markdown
Contributor Author

Sure, I'll add a box representing the bbox and update it shortly !

@AMAN194701 AMAN194701 force-pushed the add-ftface-props-thumbnail branch 4 times, most recently from d840d72 to 47c30d4 Compare March 24, 2026 22:10
The example only printed to stdout with no visual output, so Sphinx
Gallery could not auto-generate a thumbnail. Added a matplotlib figure
that visualises the font metrics (ascender, descender, bbox, underline
position/thickness) normalised to units_per_EM using the same font
loaded in the example.

Closes matplotlib#17479
@AMAN194701 AMAN194701 force-pushed the add-ftface-props-thumbnail branch from 47c30d4 to e6add6a Compare March 24, 2026 22:19
@AMAN194701
Copy link
Copy Markdown
Contributor Author

Hi @story645 — updated the figure with all the requested changes. Here's how it looks now:
Screenshot 2026-03-25 at 04 08 56

https://output.circle-artifacts.com/output/job/408bd2aa-35cd-4b4c-b0d6-098821c76163/artifacts/0/doc/build/html/gallery/misc/index.html
One thing to confirm:
The tail of 'g' doesn't touch the descender line — I also tested with 'p', 'q', 'j', 'y' and none of them touch it either. I believe this is expected because font.descender (-483 units) is a global face metric representing the maximum possible descent across ALL glyphs, not the actual descent of any specific character. Is this correct?


# ── Visualise font metrics ────────────────────────────────────────────────────
# Normalise all metrics to units_per_EM so values are in the range [-1, 1].
# This figure is used by Sphinx Gallery to auto-generate the gallery thumbnail.
Copy link
Copy Markdown
Member

@QuLogic QuLogic Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is unnecessary; all examples go in the gallery.

]

# Lines span from left edge to 72% of axes width — crossing through the glyph.
# Labels sit at 75%, clearly to the right of the lines.
Copy link
Copy Markdown
Member

@QuLogic QuLogic Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really an important word here.

Suggested change
# Labels sit at 75%, clearly to the right of the lines.
# Labels sit at 75%, to the right of the lines.

ax.set_ylim(bbox_ymin - 0.10, bbox_ymax + 0.15)
ax.set_title(
f"Font metrics — {font.family_name} {font.style_name}\n"
f"(values normalised to units_per_EM = {font.units_per_EM})",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only value shown is the underline thickness, and that one isn't normalized. I don't think this second line in the title is useful.

va='baseline', ha='center', color='black', zorder=10)

ax.set_xlim(0, 1.35)
ax.set_ylim(bbox_ymin - 0.10, bbox_ymax + 0.15)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a clever way to get the lines on the plot without having to adjust the units much, but unfortunately, it's not clever enough.

The Ag text is positioned in data units, but its size is in points and thus unrelated to the Axes in any way. You can see this immediately by resizing the figure larger vertically; the text stays the same but the ascender and bbox are farther and farther away.

You need to rescale things so that they are in the same coordinate space:

  1. Either grab the vector outline of the text and place it in the Axes as some kind of path, after scaling it so that it's the same units as the metrics (i.e., it should end up in data space just like your metrics),
  2. Or, rescale the metrics as something physical, like points or pixels, and then plot them with the text; you would probably have to switch everything to be an artist manually placed on the Figure instead of the Axes.

@AMAN194701
Copy link
Copy Markdown
Contributor Author

Thank you so much @QuLogic for detail feedback !
I've addressed this by switching to TextPath with size=1 (data units).
The glyph is now a vector path in the same data coordinate space as the normalized metrics, so resizing the figure keeps everything aligned. Let me know if there's anything else to fix !
https://output.circle-artifacts.com/output/job/8ca27233-c0db-4e31-b340-2c3bd3c136fb/artifacts/0/doc/build/html/gallery/misc/index.html
Screenshot 2026-03-28 at 02 54 01

@story645
Copy link
Copy Markdown
Member

Is there a character that would be near the ascender to show what ascender means? Like how g is showing the descender?

@AMAN194701
Copy link
Copy Markdown
Contributor Author

Hiii.. @story645

I investigated why the ascender line isn't being touched by any glyph in the current example, and I want to share my findings along with a proposed fix.


Root Cause:

The font's ascender metric (font.ascender = 1901 units) is intentionally set higher than any plain Latin letter. In DejaVu Sans Oblique:

Character Height (normalized)
font.ascender 0.928 EM
b, h, l (typographic ascender) 0.759 EM
A, H (cap height) 0.729 EM
font.descender / g tail −0.236 EM

This ~0.17 EM gap exists in every font by design — font.ascender is the font-wide ceiling, reserved to accommodate accented/diacritic characters like Â, Ĥ, É, etc. It is not the height of typical lowercase ascenders (b, d, h) or uppercase letters (A, H) — those are separate typographic concepts (ascender height and cap height respectively). Matplotlib's FT2Font does not expose cap height directly.

So no matter which plain ASCII letter is used, none will ever physically reach the ascender line. This is why g works perfectly for the descender (its tail crosses the line), but no plain letter does the same for the ascender. This is not a bug in the code — it's a character selection problem.


Proposed Fix:

Replace the current TextPath string with "Ĥg" (H with circumflex + g):

# font.ascender is the font-wide maximum height (includes accented glyphs
# like Ĥ, Â). Plain letters like b, h, l sit ~0.17 EM below it.
# Ĥ reaches ~0.926 EM ≈ ascender, just as g crosses the descender line.
# Before (current in PR)
tp = TextPath((0, 0), "Ag", size=1, prop=fp)

# After
tp = TextPath((0, 0), "Ĥg", size=1, prop=fp)

Why this works:

  • Ĥ reaches ~1896 units (0.926 EM) — almost exactly at the ascender line (0.928 EM), so it visually crosses it
  • Ĥ is the ascender-equivalent of g — just as g's tail physically crosses the descender line, Ĥ's circumflex physically crosses the ascender line. Same concept, both directions.
  • g is kept so the descender is still demonstrated
  • Both concepts (ascender + descender) are shown symmetrically in a single string

I've tested this locally and attached the output below.
Screenshot 2026-03-29 at 16 29 45


Alternative options if you prefer a different character:

  • "Ťg" — T with caron, same height as Ĥ (~0.926 EM)
  • "Ŵg" — W with circumflex, slightly exceeds the ascender (~0.936 EM)

Happy to go ahead with "Ĥg" or switch to whichever character you prefer.

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Documentation: examples files in galleries/examples

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add thumbnails for tutorials/gallery where missing

5 participants

X Tutup