Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions src/chart/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {ChartSettings, IndiView, LoadedTree} from '../gedcom/types';
import {displayName, initials, lifeSpan} from '../ui/format';
import {familyBox, marriageOrder} from './family';
import {cropBox, photoOf, type Photo} from './photo';
import {wrapName, type Measure} from './text';
import {wrapLife, wrapName, type Measure} from './text';
import {
birthYear,
decadeFill,
Expand All @@ -44,6 +44,7 @@ const NAME_TOP = PAD + PHOTO + 10 + 14; // baseline of the first name line
const NAME_LINE = 18.5;
/** Life dates are set at 13px regular; `measure` is the 15px medium name font. */
const LIFE_SCALE = 0.85;
const LIFE_LINE = 16;
/** Numbers the portraits' clip paths: each needs an id of its own. */
let clipCount = 0;

Expand Down Expand Up @@ -293,11 +294,23 @@ export class GedviewRenderer extends CompositeRenderer implements Renderer {
if (tags.length)
g.attr('aria-label', [g.attr('aria-label'), ...tags].join(', '));
if (life) {
g.append('text')
.attr('class', 'gv-life')
.attr('x', PAD)
.attr('y', NAME_TOP + (lines.length - 1) * NAME_LINE + 3 + 16)
.text(` ${life}`);
// Dates still too wide take a second line under a one-line name.
const lifeLines = wrapLife(
life,
maxText / LIFE_SCALE,
this.ctx.measure,
lines.length === 1 ? 2 : 1,
);
lifeLines.forEach((line, i) => {
g.append('text')
.attr('class', 'gv-life')
.attr('x', PAD)
.attr(
'y',
NAME_TOP + (lines.length - 1) * NAME_LINE + 19 + i * LIFE_LINE,
)
.text(` ${line}`);
});
}
// Last in the box, so the text reads in the order of the accessible
// name; drawn in the portrait row so the card keeps its height.
Expand Down
22 changes: 21 additions & 1 deletion src/chart/text.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {describe, expect, it} from 'vitest';
import {wrapName} from './text';
import {wrapLife, wrapName} from './text';

// Every character is 10 px wide.
const measure = (s: string) => s.length * 10;
Expand Down Expand Up @@ -34,3 +34,23 @@ describe('wrapName', () => {
expect(wrapName('', 100, measure)).toEqual(['']);
});
});

describe('wrapLife', () => {
// As joinRange writes it: a no-break space before the dash.
const life = 'около 1707\u00a0— между 1757 и 1758';

it('keeps dates that fit on one line', () => {
expect(wrapLife('1858—1921', 200, measure, 2)).toEqual(['1858—1921']);
});

it('breaks long dates after the dash', () => {
expect(wrapLife(life, 180, measure, 2)).toEqual([
'около 1707\u00a0—',
'между 1757 и 1758',
]);
});

it('keeps the birth and an ellipsis when one line is left', () => {
expect(wrapLife(life, 180, measure, 1)).toEqual(['около 1707\u00a0— …']);
});
});
28 changes: 28 additions & 0 deletions src/chart/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,31 @@ export function wrapName(
}
return lines.length ? lines : [''];
}

/** The dash between birth and death when either side has spaces: a
* no-break space before it, a space after (see joinRange). */
const DASH = /\u00a0(\S+) /;

/**
* Life dates in at most `maxLines` lines of at most `max` pixels. Too long
* for a line, they break after the dash, birth above and death below; with
* one line only, the birth stays and an ellipsis stands for the rest.
*/
export function wrapLife(
life: string,
max: number,
measure: Measure,
maxLines: number,
): string[] {
if (measure(life) <= max) return [life];
const m = DASH.exec(life);
if (m) {
const birth = `${life.slice(0, m.index)}\u00a0${m[1]}`;
const death = life.slice(m.index + m[0].length);
if (maxLines > 1 && measure(birth) <= max && measure(death) <= max)
return [birth, death];
if (maxLines === 1 && measure(`${birth} ${ELLIPSIS}`) <= max)
return [`${birth} ${ELLIPSIS}`];
}
return wrapName(life, max, measure, maxLines);
}
Loading