mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-14 08:11:03 +00:00
fix(studio): bold text measured at each family's own bold width, so a bold condensed heading no longer overruns its frame
This commit is contained in:
@@ -76,7 +76,12 @@ export function photoDpi(el: BookPhotoElement, info: BookPhotoInfo | undefined):
|
||||
}
|
||||
|
||||
/** Average letter width per em at weight 400, per family: the catalogue's measure (book-fonts.data.ts). */
|
||||
const letterEm = (font: string) => bookFontFace(font)?.letterEm ?? 0.5;
|
||||
/** A family's average letter width per em at a weight: its measured bold from 600 on, else a guess a little wider. */
|
||||
function letterEm(font: string, weight: number): number {
|
||||
const face = bookFontFace(font);
|
||||
const regular = face?.letterEm ?? 0.5;
|
||||
return weight >= 600 ? (face?.boldEm ?? regular * 1.06) : regular;
|
||||
}
|
||||
|
||||
/**
|
||||
* The headroom on letter width the design engine sizes a text frame with. A
|
||||
@@ -124,7 +129,7 @@ export function bookLetterMm(el: BookTextElement, headroom = 1): number {
|
||||
const sizeMm = (orDefault(el.size, 11) * 25.4) / 72;
|
||||
const tracking = orDefault(el.tracking, 0);
|
||||
const caps = bookFontFace(el.font)?.caps ? 1 : (CASE_WIDTH[el.transform ?? 'none'] ?? 1);
|
||||
return sizeMm * (letterEm(el.font) * (el.weight >= 600 ? 1.06 : 1) * caps * headroom + tracking);
|
||||
return sizeMm * (letterEm(el.font, el.weight) * caps * headroom + tracking);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,6 +58,11 @@ export interface BookFontFace {
|
||||
* the face's own advance widths, for the text measure in book-findings.ts.
|
||||
*/
|
||||
letterEm: number;
|
||||
/**
|
||||
* The same at weight 700, measured the same way, for a family that ships a
|
||||
* real bold: a condensed bold sets up to a sixth wider than its regular.
|
||||
*/
|
||||
boldEm?: number;
|
||||
/** Set in capitals whatever is typed. */
|
||||
caps?: true;
|
||||
}
|
||||
@@ -81,6 +86,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: ALL,
|
||||
italics: [],
|
||||
letterEm: 0.507,
|
||||
boldEm: 0.519,
|
||||
},
|
||||
inter: {
|
||||
name: 'Inter',
|
||||
@@ -91,6 +97,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: ALL,
|
||||
italics: [],
|
||||
letterEm: 0.478,
|
||||
boldEm: 0.491,
|
||||
},
|
||||
montserrat: {
|
||||
name: 'Montserrat',
|
||||
@@ -101,6 +108,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: ALL,
|
||||
italics: [],
|
||||
letterEm: 0.507,
|
||||
boldEm: 0.536,
|
||||
},
|
||||
// ── serif ──
|
||||
serif: {
|
||||
@@ -112,6 +120,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: MID,
|
||||
italics: MID,
|
||||
letterEm: 0.469,
|
||||
boldEm: 0.481,
|
||||
},
|
||||
garamond: {
|
||||
name: 'EB Garamond',
|
||||
@@ -122,6 +131,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: [400, 500, 600, 700, 800],
|
||||
italics: [400, 500, 600, 700, 800],
|
||||
letterEm: 0.383,
|
||||
boldEm: 0.424,
|
||||
},
|
||||
// ── elegant display serifs ──
|
||||
playfair: {
|
||||
@@ -133,6 +143,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: [400, 500, 600, 700, 800, 900],
|
||||
italics: [400, 500, 600, 700, 800, 900],
|
||||
letterEm: 0.454,
|
||||
boldEm: 0.462,
|
||||
},
|
||||
fraunces: {
|
||||
name: 'Fraunces',
|
||||
@@ -143,6 +154,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: ALL,
|
||||
italics: ALL,
|
||||
letterEm: 0.467,
|
||||
boldEm: 0.497,
|
||||
},
|
||||
cormorant: {
|
||||
name: 'Cormorant Garamond',
|
||||
@@ -153,6 +165,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: [300, 400, 500, 600, 700],
|
||||
italics: [300, 400, 500, 600, 700],
|
||||
letterEm: 0.393,
|
||||
boldEm: 0.398,
|
||||
},
|
||||
dmserif: {
|
||||
name: 'DM Serif Display',
|
||||
@@ -173,6 +186,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: [400, 500, 600, 700, 800, 900],
|
||||
italics: [400, 500, 600, 700, 800, 900],
|
||||
letterEm: 0.463,
|
||||
boldEm: 0.488,
|
||||
},
|
||||
// ── condensed and poster ──
|
||||
bebas: {
|
||||
@@ -205,6 +219,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: [200, 300, 400, 500, 600, 700],
|
||||
italics: [],
|
||||
letterEm: 0.365,
|
||||
boldEm: 0.412,
|
||||
},
|
||||
league: {
|
||||
name: 'League Gothic',
|
||||
@@ -225,6 +240,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: ALL,
|
||||
italics: [],
|
||||
letterEm: 0.304,
|
||||
boldEm: 0.355,
|
||||
},
|
||||
archivo: {
|
||||
name: 'Archivo Black',
|
||||
@@ -246,6 +262,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: ALL,
|
||||
italics: [],
|
||||
letterEm: 0.492,
|
||||
boldEm: 0.5,
|
||||
},
|
||||
shrikhand: {
|
||||
name: 'Shrikhand',
|
||||
@@ -276,6 +293,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: [200, 300, 400, 500, 600, 700, 800],
|
||||
italics: [],
|
||||
letterEm: 0.471,
|
||||
boldEm: 0.49,
|
||||
},
|
||||
abril: {
|
||||
name: 'Abril Fatface',
|
||||
@@ -297,6 +315,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: MID,
|
||||
italics: [],
|
||||
letterEm: 0.344,
|
||||
boldEm: 0.349,
|
||||
},
|
||||
dancing: {
|
||||
name: 'Dancing Script',
|
||||
@@ -307,6 +326,7 @@ export const BOOK_FONT_FACES = {
|
||||
weights: MID,
|
||||
italics: [],
|
||||
letterEm: 0.363,
|
||||
boldEm: 0.372,
|
||||
},
|
||||
pacifico: {
|
||||
name: 'Pacifico',
|
||||
|
||||
@@ -71,6 +71,12 @@ describe('the catalogue', () => {
|
||||
for (const w of face.italics) expect(face.weights, `${id} italic ${w}`).toContain(w);
|
||||
expect(face.letterEm, id).toBeGreaterThan(0.2);
|
||||
expect(face.letterEm, id).toBeLessThan(0.7);
|
||||
// A measured bold only where the family ships one, and never narrower than its regular.
|
||||
const bold = (face as { boldEm?: number }).boldEm;
|
||||
if (bold !== undefined) {
|
||||
expect(face.weights as readonly number[], id).toContain(700);
|
||||
expect(bold, id).toBeGreaterThanOrEqual(face.letterEm);
|
||||
}
|
||||
expect(face.roles.length, id).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { textHeightNeeded } from './book-findings';
|
||||
import { bookLetterMm, textHeightNeeded } from './book-findings';
|
||||
import { blankBook } from './book-pages';
|
||||
import {
|
||||
bookCasedText,
|
||||
@@ -138,4 +138,12 @@ describe('fitting a page', () => {
|
||||
expect((next.spreads[0]?.elements[0] as BookTextElement).size).toBeLessThan(60);
|
||||
expect(next.spreads[1]).toBe(doc.spreads[1]);
|
||||
});
|
||||
|
||||
it('BKFIT-011: a bold condensed face is measured at its own width, not the regular widened by a guess', () => {
|
||||
const oswald = (weight: number) => bookLetterMm(text({ font: 'oswald', weight, size: 20 }));
|
||||
// Oswald's 700 sets about 13% wider than its 400; a flat 6% would let a bold heading overrun its frame.
|
||||
expect(oswald(700) / oswald(400)).toBeGreaterThan(1.1);
|
||||
const vibes = (weight: number) => bookLetterMm(text({ font: 'vibes', weight, size: 20 }));
|
||||
expect(vibes(700) / vibes(400)).toBeCloseTo(1.06, 6);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user