07e5a2c08f148d8bf31219af8c2218cd2cc893cf braney Wed Jun 3 09:16:15 2026 -0700 hgTracks config: pair FreeType font names with their files in one table The configure-page font list lived in two parallel arrays, freeTypeFontNames[] and freeTypeFontFiles[], matched only by position. Nothing tied a name to its file, and maybeNewFonts() looked a name up in the first array and then indexed the second with no bounds check -- so any drift in order or count between the two, or a selected name that matched nothing, silently rendered the wrong font or read past the end of the array. Merge the two arrays into a single struct freeTypeFont[] of {name, file} rows so the two can no longer get out of sync, and make maybeNewFonts() fall back to the bitmap engine when the selected font isn't one we know instead of indexing out of bounds. Same fonts and same name-to-file mapping as before. Also fix faceNames[] to size by element count (ArraySize) rather than byte count (sizeof). refs #37698 Co-Authored-By: Claude Opus 4.8 (1M context) diff --git src/hg/hgTracks/config.c src/hg/hgTracks/config.c index 34d377022ab..086369c4a7d 100644 --- src/hg/hgTracks/config.c +++ src/hg/hgTracks/config.c @@ -46,153 +46,138 @@ char* name = el->name; name = chopPrefix(name); // chop off first three words name = chopPrefix(name); name = chopPrefix(name); replaceChar(name, '_', ' '); labels[i] = name; i++; } char* currentTheme = cartOptionalString(cart, "theme"); hDropList("theme", labels, i, currentTheme); slFreeList(themes); hPrintf(""); } -char *freeTypeFontNames[] = { -"AvantGarde-Book", -"AvantGarde-Demi", -"AvantGarde-BookOblique", -"AvantGarde-DemiOblique", -"Helvetica", -"Helvetica-Bold", -"Helvetica-Oblique", -"Helvetica-BoldOblique", -"Helvetica-Narrow", -"Helvetica-Narrow-Bold", -"Helvetica-Narrow-Oblique", -"Helvetica-Narrow-BoldOblique", -"Times-Roman", -"Times-Bold", -"Times-Italic", -"Times-BoldItalic", -"Courier", -"Courier-Bold", -"Courier-Oblique", -"Courier-BoldOblique", -"ZapfChancery-MediumItalic", -"Atkinson", -"Atkinson-Bold", -"Atkinson-Oblique", -"Atkinson-BoldOblique", -"Lexend", -"Lexend-Bold", +struct freeTypeFont +/* A font offered to the FreeType text engine. The name and its file live on + * one row so the two can never drift out of sync (this used to be two parallel + * arrays indexed by position, which silently rendered the wrong font when they + * disagreed). */ + { + char *name; /* Label shown in the configure-page dropdown. A normal-weight + * font is just the face ("Lexend"); a variant is "Face-Style" + * ("Lexend-Bold"). The dropdown splits this on the first '-'. */ + char *file; /* Font file (.pfb Type-1 or .ttf TrueType) found under + * freeTypeDir (hg.conf freeTypeDir, default ../htdocs/urw-fonts). */ }; -char *freeTypeFontFiles[] = { -"a010013l.pfb", -"a010015l.pfb", -"a010033l.pfb", -"a010035l.pfb", -"n019003l.pfb", -"n019004l.pfb", -"n019023l.pfb", -"n019024l.pfb", -"n019043l.pfb", -"n019044l.pfb", -"n019063l.pfb", -"n019064l.pfb", -"n021003l.pfb", -"n021004l.pfb", -"n021023l.pfb", -"n021024l.pfb", -"n022003l.pfb", -"n022004l.pfb", -"n022023l.pfb", -"n022024l.pfb", -"z003034l.pfb", -"AtkinsonHyperlegible-Regular.ttf", -"AtkinsonHyperlegible-Bold.ttf", -"AtkinsonHyperlegible-Italic.ttf", -"AtkinsonHyperlegible-BoldItalic.ttf", -"Lexend-Regular.ttf", -"Lexend-Bold.ttf", +struct freeTypeFont freeTypeFonts[] = { +{"AvantGarde-Book", "a010013l.pfb"}, +{"AvantGarde-Demi", "a010015l.pfb"}, +{"AvantGarde-BookOblique", "a010033l.pfb"}, +{"AvantGarde-DemiOblique", "a010035l.pfb"}, +{"Helvetica", "n019003l.pfb"}, +{"Helvetica-Bold", "n019004l.pfb"}, +{"Helvetica-Oblique", "n019023l.pfb"}, +{"Helvetica-BoldOblique", "n019024l.pfb"}, +{"Helvetica-Narrow", "n019043l.pfb"}, +{"Helvetica-Narrow-Bold", "n019044l.pfb"}, +{"Helvetica-Narrow-Oblique", "n019063l.pfb"}, +{"Helvetica-Narrow-BoldOblique", "n019064l.pfb"}, +{"Times-Roman", "n021003l.pfb"}, +{"Times-Bold", "n021004l.pfb"}, +{"Times-Italic", "n021023l.pfb"}, +{"Times-BoldItalic", "n021024l.pfb"}, +{"Courier", "n022003l.pfb"}, +{"Courier-Bold", "n022004l.pfb"}, +{"Courier-Oblique", "n022023l.pfb"}, +{"Courier-BoldOblique", "n022024l.pfb"}, +{"ZapfChancery-MediumItalic", "z003034l.pfb"}, +{"Atkinson", "AtkinsonHyperlegible-Regular.ttf"}, +{"Atkinson-Bold", "AtkinsonHyperlegible-Bold.ttf"}, +{"Atkinson-Oblique", "AtkinsonHyperlegible-Italic.ttf"}, +{"Atkinson-BoldOblique", "AtkinsonHyperlegible-BoldItalic.ttf"}, +{"Lexend", "Lexend-Regular.ttf"}, +{"Lexend-Bold", "Lexend-Bold.ttf"}, }; char *emptyStyles[] = { "Normal" }; static boolean freeTypeOn() { #ifdef USE_FREETYPE char *defaultState = "on"; #else // USE_FREETYPE char *defaultState = "off"; #endif // USE_FREETYPE return sameString(cfgOptionDefault("freeType", defaultState), "on"); } void maybeNewFonts(struct hvGfx *hvg) /* Check to see if we want to use the alternate font engine (FreeType2). */ { if (!freeTypeOn()) return; if (sameString(tl.textFont, "Bitmap")) return; char *fontDir = cfgOptionDefault("freeTypeDir", "../htdocs/urw-fonts"); char buffer[4096]; int ii; -for(ii=0; ii < ArraySize(freeTypeFontNames); ii++) - if (sameString(freeTypeFontNames[ii], tl.textFont)) +for(ii=0; ii < ArraySize(freeTypeFonts); ii++) + if (sameString(freeTypeFonts[ii].name, tl.textFont)) break; -char *fontFile = freeTypeFontFiles[ii]; -char *fontName = freeTypeFontNames[ii]; +if (ii == ArraySize(freeTypeFonts)) + return; // not a font we know about; leave the bitmap engine in place +char *fontFile = freeTypeFonts[ii].file; +char *fontName = freeTypeFonts[ii].name; safef(buffer, sizeof buffer, "%s/%s", fontDir, fontFile); hvGfxSetFontMethod(hvg, FONT_METHOD_FREETYPE, fontName, buffer ); } static void textFontDropDown() /* Create drop down for font size. */ { /* get current values for font and style */ char *currentFontName = cloneString(tl.textFont); char *currentStyle = strchr(currentFontName, '-'); if (currentStyle) *currentStyle++ = 0; else currentStyle = "Normal"; -char *faceNames[sizeof(freeTypeFontNames)]; +char *faceNames[ArraySize(freeTypeFonts) + 1]; // +1 for the "Bitmap" entry below int ii; int numFonts = 0; struct dyString *dy = dyStringNew(1024); dyStringPrintf(dy, " fontStyles = [];\n"); int numStyle = 0; char *lastName = NULL; faceNames[numFonts++] = "Bitmap"; dyStringPrintf(dy, " fontStyles['Bitmap'] = ['Normal'];"); -for (ii=0; ii < ArraySize(freeTypeFontNames); ii++) +for (ii=0; ii < ArraySize(freeTypeFonts); ii++) { - char *fontName = cloneString(freeTypeFontNames[ii]); + char *fontName = cloneString(freeTypeFonts[ii].name); char *style = strchr(fontName, '-'); if (style) *style++ = 0; if ((lastName == NULL) || differentString(lastName, fontName)) { faceNames[numFonts] = fontName; if (lastName != NULL) dyStringPrintf(dy, " ];\n"); dyStringPrintf(dy, " fontStyles['%s'] = [", fontName); numStyle = 0; numFonts++; }