Skip to content

Commit

Permalink
better hyphenation + demo settings storage
Browse files Browse the repository at this point in the history
  • Loading branch information
nclslbrn committed Jun 1, 2024
1 parent 5c4a8bc commit ebdb382
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 45 deletions.
1 change: 0 additions & 1 deletion demo/src/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export type OnChange = (name: string, val: boolean | string | number) => void;

const trueFalseCheckbox = (name: string, val: boolean, parent: HTMLElement, onchange: OnChange) => {
const fieldset = document.createElement('fieldset');

const field = document.createElement('input');
field.type = 'checkbox';

Expand Down
79 changes: 54 additions & 25 deletions demo/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
import { type Char, getGlyphPath, getParagraphPath } from '../../src/index.ts';
import {
type Char,
Line,
Vec,
getGlyphPath,
getParagraphPath,
getParagraphVector,
} from '../../src/index.ts';
import { inputRange, textarea, button } from './field.ts';
import { togglablePanel } from './panel.ts';
import { name, version } from '../../package.json';
import quotes from './quotes.ts';

let settings;

const app = document.getElementById('app'),
header = document.createElement('header'),
[settingsPanel, openSettingsPanel] = togglablePanel(false, '☰', '✖'),
namespace = 'http://www.w3.org/2000/svg',
svg = document.createElementNS(namespace, 'svg'),
settings = {
Text: quotes[Math.floor(Math.random() * quotes.length)],
'Letters per line': 30,
'Letter spacing': 1,
'Line spacing': 0.92,
pathFromD = (d: string): SVGPathElement => {
const path = document.createElementNS(namespace, 'path');
path.setAttribute('d', d);
return path;
},
updateSetting = (propName: any, propValue: any) => {
settings[propName] = propValue;
localStorage.setItem(propName, String(propValue));
render();
},
getSetting = (propName: any): string =>
localStorage.getItem(propName) !== null ? <string>localStorage.getItem(propName) : '',
group = document.createElementNS(namespace, 'g');

const pathFromD = (d: string): SVGPathElement => {
const path = document.createElementNS(namespace, 'path');
path.setAttribute('d', d);
return path;
settings = {
// These settings will be store in the local storage if user change them
Text: getSetting('Text') || quotes[Math.floor(Math.random() * quotes.length)],
'Letters per line':
getSetting('Letters per line') === '' ? 30 : parseInt(getSetting('Letters per line')),
'Letter spacing':
getSetting('Letter spacing') === '' ? 0.95 : parseFloat(getSetting('Letter spacing')),
'Line spacing': getSetting('Line spacing') === '' ? 0.92 : parseFloat(getSetting('Line spacing')),
};

const init = () => {
Expand All @@ -42,12 +61,12 @@ const init = () => {
header.appendChild(svgLogo);
header.appendChild(openSettingsPanel);

textarea('Text', settings.Text, settingsPanel, updateSettings);
textarea('Text', settings.Text, settingsPanel, updateSetting);
inputRange(
'Letters per line',
settings['Letters per line'],
settingsPanel,
updateSettings,
updateSetting,
12,
120,
1
Expand All @@ -56,7 +75,7 @@ const init = () => {
'Letter spacing',
settings['Letter spacing'],
settingsPanel,
updateSettings,
updateSetting,
0.7,
1.4,
0.01
Expand All @@ -65,7 +84,7 @@ const init = () => {
'Line spacing',
settings['Line spacing'],
settingsPanel,
updateSettings,
updateSetting,
0.5,
1.4,
0.01
Expand Down Expand Up @@ -108,25 +127,35 @@ const render = () => {
width = window.innerWidth - 40;

group.textContent = '';
/** using getParagraphPath */
const textBlock = getParagraphPath(userInput, settings['Letters per line'], 5, width, [
settings['Letter spacing'],
settings['Line spacing'],
]);
textBlock.paths.forEach((d: string) => {
const path = document.createElementNS(namespace, 'path');
path.setAttribute('d', d);
group.appendChild(path);
});
textBlock.paths.forEach((d: string) => group.appendChild(pathFromD(d)));

/* using getParagraphVector
const textBlock = getParagraphVector(userInput, settings['Letters per line'], 5, width, [
settings['Letter spacing'],
settings['Line spacing'],
]);
textBlock.vectors.forEach((g) =>
g.forEach((l: Line) => {
const path = document.createElementNS(namespace, 'path');
path.setAttribute(
'd',
l.reduce(
(com: string, v: Vec, i: number) =>
(com += `${i === 0 ? 'M' : 'L'}${v[0]},${v[1]}${i === l.length - 1 ? '' : ' '}`),
''
)
);
group.appendChild(path);
})
);*/
//group.setAttribute('stroke-width', width < 800 ? '0.5' : '2');
svg.setAttribute('width', `${width}`);
svg.setAttribute('height', `${textBlock.height + 40}`);
svg.setAttribute('viewbox', `0 0 ${width} ${height + 40}`);
};

const updateSettings = (propName: any, propValue: any) => {
settings[propName] = propValue;
render();
};

init();
10 changes: 5 additions & 5 deletions demo/src/quotes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default [
`“Without this faculty of man and beast alike to recognize identities across the variations of difference, to make allowance for changed conditions, and to preserve the framework of a stable world, art could not exist. When we open our eyes under water we recognize objects, shapes, and colors although through an unfamiliar medium. When we first see pictures we see them in an unfamiliar medium. This is more than a mere pun. The two capacities are interrelated. Every time we meet with an unfamiliar type of transposition, there is a brief moment of shock and a period of adjustment-but it is an adjustment for which the mechanism exists in us.”\r\n
`“Without this faculty of man and beast alike to recognize identities across the variations of difference, to make allowance for changed conditions, and to preserve the framework of a stable world, art could not exist. When we open our eyes under water we recognize objects, shapes, and colors although through an unfamiliar medium. When we first see pictures we see them in an unfamiliar medium. This is more than a mere pun. The two capacities are interrelated. Every time we meet with an unfamiliar type of transposition, there is a brief moment of shock and a period of adjustment-but it is an adjustment for which the mechanism exists in us.”\r\n
― E.H. Gombrich, Art and Illusion: A Study in the Psychology of Pictorial Representation`,
`“The artist, no less than the writer, needs a vocabulary before he can embark on a "copy" of reality.”\r\n
― E.H. Gombrich, Art and Illusion: A Study in the Psychology of Pictorial Representation`,
Expand All @@ -10,12 +10,12 @@ export default [
`“There is no reality without interpretation; just as there is no innocent eye, there is no innocent ear.”\r\n
― E.H. Gombrich, Art and Illusion: A Study in the Psychology of Pictorial Representation`,

`The first question we should ask ourselves when looking at a work of art is: – Does it give me the chance to exist in front of it, or, on the contrary, does it deny me as a subject, refusing the consider the Other in its structure? Does the space-time factor suggested or described by this work, together with the laws governing it, tally with my aspirations in real life? Does it criticise what is deemed to be criticisable? Could I live in a space-time structure corresponding to this reality?\r\n
― N. BOURRIAUD, Relational Aesthetics`,
`“When an artist uses a conceptual form of art, it means that all of the planning and decisions are made beforehand and the execution is a perfunctory affair. The result is a theatrical event.”\r\n
― Sol Lewitt`,

`“Everything we see hides another thing, we always want to see what is hidden by what we see. There is an interest in that which is hidden and which the visible does not show us. This interest can take the form of a quite intense feeling, a sort of conflict, one might say, between the visible that is hidden and the visible that is present.”\r\n
― René Magritte`,
];

`The electronic image is not fixed to any material base and, like our DNA, it has become a code that can circulate to any container that will hold it, defying death as it travels at the speed of light.\r\n
― Bill Viola`
]
60 changes: 46 additions & 14 deletions src/writer/blockWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,35 @@ const charArray = (text: string, charsPerLine: number, hyphenFrom: number): Arra
const paragraphs: Array<string> = text.split(/\r?\n|\r|\n/g);

for (let h = 0; h < paragraphs.length; h++) {
// Separate each word of the paragraph
// Split each word of the paragraph
const words: Array<string> = paragraphs[h].split(' ');
for (let i = 0; i < words.length; i++) {
// Split each letter of word
const letters = words[i].split('') as Array<Char>,
wordEnd = x + letters.length;

// word on multiple line (add hyphen)
if (wordEnd > charsPerLine) {
// Word cut (multiple line)
if (wordEnd >= charsPerLine) {
for (let j = 0, l = letters[0]; j < letters.length; j++, l = letters[j]) {
// word close the line
// word ends at the end of the line
if (x === charsPerLine - 1 && i === letters.length - 1) {
grid[y].push(l);
y++;
x = 0;
grid.push([]);
}
// the next letter is a punctuation mark
else if (x + 1 == charsPerLine && j < letters.length - 1 && isPuncChar(letters[j + 1])) {
else if (x + 2 > charsPerLine && j < letters.length - 1 && isPuncChar(letters[j + 1])) {
grid[y].push(...[l, letters[j + 1]]);
j++;
y++;
x = 0;
grid.push([]);
}
// end line (create new line)
// soft hyphenation between vowel and consonant (create new line)
// test it before applying hard hyphenation to word
else if (
x + 4 > charsPerLine &&
x + hyphenFrom > charsPerLine &&
j < letters.length - 1 &&
(!isConsonant(l) || isConsonant(letters[j + 1]))
) {
Expand All @@ -57,15 +59,30 @@ const charArray = (text: string, charsPerLine: number, hyphenFrom: number): Arra
x = 0;
grid.push([]);
}
// before the line end
else {
// hard hyphenation (no rules just prevent line larger than the limit)
else if (x + 1 > charsPerLine) {
grid[y].push(...[l as Char, '-' as Char]);
y++;
x = 0;
grid.push([]);
} else if (x + 1 < charsPerLine) {
/* before the line end */
grid[y].push(l);
if (j == letters.length - 1) {
grid[y].push(' ' as Char);
x++;
}
x++;
}
// put the entire word on the next line
else {
const erase = letters.length - j;
grid[y] = grid[y].splice(0, grid[y].length - erase);
y++;
grid[y] = [...[...letters, ' ' as Char]];
x = letters.length;
break;
}
}
}
// word close the line (add space to close the line)
Expand All @@ -77,7 +94,7 @@ const charArray = (text: string, charsPerLine: number, hyphenFrom: number): Arra
x = 0;
}
// word on same line and sufficient room (add space after word)
else if (wordEnd < charsPerLine - hyphenFrom) {
else if (wordEnd < charsPerLine) {
grid[y].push(...[...letters, ' ' as Char]);
x += letters.length + 1;
}
Expand Down Expand Up @@ -106,19 +123,25 @@ const getParagraphVector = (
text: string,
charsPerLine: number,
hyphenFrom: number,
textWidth: number
): Glyph[] => {
textWidth: number,
spacing = [1, 1]
): { vectors: Glyph[]; height: number } => {
const grid = charArray(text, charsPerLine, hyphenFrom);
const textSize = [textWidth / charsPerLine, (textWidth / charsPerLine) * 1.4];
return grid.reduce(
const invSpacing = [(1 / spacing[0]) * textSize[0], (1 / spacing[1]) * textSize[1]];
const vectors = grid.reduce(
(out: Glyph[], row: Array<Char>, y: number) => [
...out,
...row.map((l: Char, x: number) =>
getGlyphVector(l, textSize, [x * textSize[0], y * textSize[1]])
getGlyphVector(l, invSpacing, [x * textSize[0], y * textSize[1]])
),
],
[]
);
return {
vectors,
height: grid.length * textSize[1],
};
};

/**
Expand Down Expand Up @@ -157,6 +180,15 @@ const getParagraphPath = (
] as string[],
[] as string[]
);

/** debug charsPerLine
console.log(
`Line > ${charsPerLine}`,
grid
.filter((lin) => lin.length > charsPerLine)
.map((len)=> grid.indexOf(len))
)
*/
return { paths, height: textSize[1] * grid.length };
};

Expand Down

0 comments on commit ebdb382

Please sign in to comment.