Skip to content

Commit 517eca7

Browse files
authored
Merge pull request #91 from basil/frog-prince-test
Increase test coverage
2 parents ff908d0 + ab31d5a commit 517eca7

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed

test/layout-test.ts

+190
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,138 @@ function itemsFromString(s: string, charWidth: number, glueStretch: number): Tex
107107
return items;
108108
}
109109

110+
function charWidth(char: string): number {
111+
// Traditional Monotype character widths in machine units (1/18th of an em)
112+
// from p. 75 of Digital Typography
113+
if (char.length !== 1) {
114+
throw new Error(`Input is not a single character: ${char}`);
115+
}
116+
switch (char) {
117+
case 'i':
118+
case 'l':
119+
case ',':
120+
case '.':
121+
case ';':
122+
case '’':
123+
return 5;
124+
case 'f':
125+
case 'j':
126+
case 'I':
127+
case '-':
128+
case '\u00ad':
129+
return 6;
130+
case 'r':
131+
case 's':
132+
case 't':
133+
return 7;
134+
case 'c':
135+
case 'e':
136+
case 'z':
137+
return 8;
138+
case 'a':
139+
case 'g':
140+
case 'o':
141+
case 'v':
142+
return 9;
143+
case 'b':
144+
case 'd':
145+
case 'h':
146+
case 'k':
147+
case 'n':
148+
case 'p':
149+
case 'q':
150+
case 'u':
151+
case 'x':
152+
case 'y':
153+
return 10;
154+
case 'w':
155+
case 'C':
156+
return 13;
157+
case 'm':
158+
return 15;
159+
default:
160+
throw new Error(`Unsupported character: ${char.charCodeAt(0)}`);
161+
}
162+
}
163+
164+
function frogPrinceItemsImpl(
165+
text: string,
166+
prologue: TextInputItem[],
167+
betweenWords: (c: string) => TextInputItem[],
168+
epilogue: TextInputItem[],
169+
): TextInputItem[] {
170+
const result: TextInputItem[] = [];
171+
let buf = '';
172+
let width = 0;
173+
let lastC = '*';
174+
175+
result.push(...prologue);
176+
177+
for (const c of text) {
178+
if (['-', '\u00AD', ' '].includes(c)) {
179+
if (buf !== '') {
180+
result.push({ type: 'box', width, text: buf } as TextBox);
181+
buf = '';
182+
width = 0;
183+
}
184+
}
185+
186+
switch (c) {
187+
case ' ':
188+
result.push(...betweenWords(lastC));
189+
break;
190+
case '-':
191+
result.push({ type: 'box', width: charWidth(c), text: '-' } as TextBox);
192+
result.push({ type: 'penalty', width: 0, cost: 50, flagged: true });
193+
break;
194+
case '\u00AD':
195+
// Soft hyphen
196+
result.push({ type: 'penalty', width: charWidth(c), cost: 50, flagged: true });
197+
break;
198+
default:
199+
buf += c;
200+
width += charWidth(c);
201+
break;
202+
}
203+
204+
lastC = c;
205+
}
206+
207+
if (buf !== '') {
208+
result.push({ type: 'box', width, text: buf });
209+
}
210+
211+
result.push(...epilogue);
212+
213+
return result;
214+
}
215+
216+
const frogPrinceText =
217+
'In olden times when wish\u00ading still helped one, there lived a king whose daugh\u00adters were all beau\u00adti\u00adful; and the young\u00adest was so beau\u00adti\u00adful that the sun it\u00adself, which has seen so much, was aston\u00adished when\u00adever it shone in her face. Close by the king’s castle lay a great dark for\u00adest, and un\u00adder an old lime-tree in the for\u00adest was a well, and when the day was very warm, the king’s child went out into the for\u00adest and sat down by the side of the cool foun\u00adtain; and when she was bored she took a golden ball, and threw it up on high and caught it; and this ball was her favor\u00adite play\u00adthing.';
218+
219+
function frogPrinceItems(): TextInputItem[] {
220+
// Built as described on p. 75 of Digital Typography
221+
const prologue: TextInputItem[] = [];
222+
const betweenWords = (c: string): TextInputItem[] => {
223+
switch (c) {
224+
case ',':
225+
return [{ type: 'glue', width: 6, stretch: 4, shrink: 2, text: ' ' } as TextGlue];
226+
case ';':
227+
return [{ type: 'glue', width: 6, stretch: 4, shrink: 1, text: ' ' } as TextGlue];
228+
case '.':
229+
return [{ type: 'glue', width: 8, stretch: 6, shrink: 1, text: ' ' } as TextGlue];
230+
default:
231+
return [{ type: 'glue', width: 6, stretch: 3, shrink: 2, text: ' ' } as TextGlue];
232+
}
233+
};
234+
const epilogue: TextInputItem[] = [
235+
{ type: 'penalty', width: 0, cost: 1000, flagged: false },
236+
{ type: 'glue', width: 0, stretch: 1000, shrink: 0, text: '' } as TextGlue,
237+
{ type: 'penalty', width: 0, cost: -1000, flagged: true },
238+
];
239+
return frogPrinceItemsImpl(frogPrinceText, prologue, betweenWords, epilogue);
240+
}
241+
110242
describe('layout', () => {
111243
describe('breakLines', () => {
112244
it('returns an empty list if the input is empty', () => {
@@ -119,6 +251,64 @@ describe('layout', () => {
119251
assert.deepEqual(breakpoints, [0]);
120252
});
121253

254+
it('generates narrow frog prince layout from p. 81 of Digital Typography', () => {
255+
const items = frogPrinceItems();
256+
// width given on p. 78 of Digital Typography
257+
// subtract 1em (18 machine units) from the first line
258+
const lineLengths = [372, ...Array(items.length - 1).fill(390)];
259+
const breakpoints = breakLines(items, lineLengths);
260+
const lines = lineStrings(items, breakpoints);
261+
assert.deepEqual(lines, [
262+
'In olden times when wishing still helped one,',
263+
'there lived a king whose daughters were all beau-',
264+
'tiful; and the youngest was so beautiful that the',
265+
'sun itself, which has seen so much, was aston-',
266+
'ished whenever it shone in her face. Close by the',
267+
'king’s castle lay a great dark forest, and under an',
268+
'old limetree in the forest was a well, and when',
269+
'the day was very warm, the king’s child went out',
270+
'into the forest and sat down by the side of the',
271+
'cool fountain; and when she was bored she took a',
272+
'golden ball, and threw it up on high and caught',
273+
'it; and this ball was her favorite plaything. -',
274+
]);
275+
const adjRatios = adjustmentRatios(items, lineLengths, breakpoints).map((num) =>
276+
Number(num.toFixed(3)),
277+
);
278+
assert.deepEqual(
279+
adjRatios,
280+
[0.857, 0.0, 0.28, 1.0, 0.067, -0.278, 0.536, -0.167, 0.7, -0.176, 0.357, 0.049],
281+
);
282+
});
283+
284+
it('generates wide frog prince layout from p. 82 of Digital Typography', () => {
285+
const items = frogPrinceItems();
286+
// width given on p. 81 of Digital Typography
287+
// subtract 1em (18 machine units) from the first line
288+
const lineLengths = [482, ...Array(items.length - 1).fill(500)];
289+
const breakpoints = breakLines(items, lineLengths);
290+
const lines = lineStrings(items, breakpoints);
291+
assert.deepEqual(lines, [
292+
'In olden times when wishing still helped one, there lived a',
293+
'king whose daughters were all beautiful; and the youngest was',
294+
'so beautiful that the sun itself, which has seen so much, was',
295+
'astonished whenever it shone in her face. Close by the king’s',
296+
'castle lay a great dark forest, and under an old limetree in the',
297+
'forest was a well, and when the day was very warm, the king’s',
298+
'child went out into the forest and sat down by the side of the',
299+
'cool fountain; and when she was bored she took a golden ball,',
300+
'and threw it up on high and caught it; and this ball was her',
301+
'favorite plaything. -',
302+
]);
303+
const adjRatios = adjustmentRatios(items, lineLengths, breakpoints).map((num) =>
304+
Number(num.toFixed(3)),
305+
);
306+
assert.deepEqual(
307+
adjRatios,
308+
[0.774, 0.179, 0.629, 0.545, 0.0, 0.079, 0.282, 0.294, 0.575, 0.353],
309+
);
310+
});
311+
122312
it('generates expected layout', () => {
123313
const f = readLayoutFixture(fixture);
124314
f.outputs.forEach(({ lines, layoutOptions }) => {

0 commit comments

Comments
 (0)