break-styled-lines
Advanced tools
Comparing version 1.1.2 to 1.2.0
@@ -1,45 +0,8 @@ | ||
function isArray(text) { | ||
return Array.isArray(text); | ||
function isStringArray(text) { | ||
return Array.isArray(text) && text.every((member) => typeof member === 'string'); | ||
} | ||
function insertNewlineAtPosition(position, arrayOfStrings) { | ||
const { indexToInsertInto, localPosition } = arrayOfStrings.reduce(({ indexToInsertInto, localPosition, lengthOfPreceding }, string, i) => { | ||
const totalLength = string.length + lengthOfPreceding; | ||
if (!indexToInsertInto && !localPosition && position < totalLength) { | ||
return { | ||
indexToInsertInto: i, | ||
localPosition: position - lengthOfPreceding, | ||
lengthOfPreceding: totalLength | ||
}; | ||
} | ||
return { | ||
indexToInsertInto, | ||
localPosition, | ||
lengthOfPreceding: totalLength | ||
}; | ||
}, { indexToInsertInto: 0, localPosition: 0, lengthOfPreceding: 0 }); | ||
return (arrayOfStrings | ||
.map((string, i) => { | ||
if (i === indexToInsertInto) { | ||
return (string.slice(0, localPosition) + "\n" + string.slice(localPosition)); | ||
} | ||
return string; | ||
}) | ||
// This is to remove whitespace adjacent to newlines, but to preserve starting or trailing whitespace | ||
.map(string => string | ||
.split("\n") | ||
.map((str, i, strs) => { | ||
if (i < strs.length - 2 && i > 0) { | ||
return str.trim(); | ||
} | ||
if (i < strs.length - 2) { | ||
return str.trimRight(); | ||
} | ||
else if (i > 0) { | ||
return str.trimLeft(); | ||
} | ||
return str; | ||
}) | ||
.join("\n"))); | ||
function isTextDescriptorArray(text) { | ||
return Array.isArray(text) && !isStringArray(text); | ||
} | ||
function breakLines(text, width, font) { | ||
function breakLines(descriptors, width) { | ||
const supportsOffscreenCanvas = "OffscreenCanvas" in window; | ||
@@ -53,25 +16,44 @@ const canvasEl = document.createElement("canvas"); | ||
if (ctx) { | ||
ctx.font = font; | ||
const brokenWords = text.split(" ").reduce((accumulator, word) => { | ||
// get the last element of the accumulator | ||
const [lastLine] = accumulator.slice(-1); | ||
// add the word to it | ||
const maybeNextLine = [...lastLine, word].join(" "); | ||
// see if it fits within the width | ||
let { width: textWidth } = ctx.measureText(maybeNextLine); | ||
// if it does, append to the last element | ||
if (textWidth <= width) { | ||
return [...accumulator.slice(0, -1), [...lastLine, word]]; | ||
} | ||
if (lastLine.length === 0) { | ||
return [...accumulator.slice(0, -1), [word]]; | ||
} | ||
// if not, create a new array containing the word as the last element | ||
return [...accumulator, [word]]; | ||
}, [[]]); | ||
return brokenWords.map(line => line.join(" ")).join("\n"); | ||
return descriptors.reduce((result, { text, font }, descIndex) => { | ||
const lastLine = result[result.length - 1]; | ||
const lastLineSplit = lastLine ? lastLine.split('\n') : []; | ||
const runningLine = lastLineSplit.length > 0 ? lastLineSplit[lastLineSplit.length - 1] : ""; | ||
const prevFont = descriptors[descIndex - 1] ? descriptors[descIndex - 1].font : font; | ||
const brokenWords = text.split(" ").reduce((accumulator, word, i) => { | ||
// get the last element of the accumulator | ||
const [lastLine] = accumulator.slice(-1); | ||
// add the word to it | ||
const maybeNextLine = [...lastLine, word].join(" "); | ||
// see if it fits within the width | ||
ctx.font = prevFont; | ||
const { width: prevTextWidth } = ctx.measureText(runningLine); | ||
ctx.font = font; | ||
const { width: textWidth } = ctx.measureText(maybeNextLine); | ||
const totalWidth = i === 0 ? prevTextWidth + textWidth : textWidth; | ||
// if it does, append to the last element | ||
if (totalWidth <= width) { | ||
return [...accumulator.slice(0, -1), [...lastLine, word]]; | ||
} | ||
// Handle case of the first word being too long for a line | ||
if (totalWidth > width && i === 0 && runningLine === "") { | ||
return [...accumulator.slice(0, -1), [...lastLine, word]]; | ||
} | ||
// if not, create a new array containing the word as the last element | ||
return [...accumulator, [word]]; | ||
}, [[]]); | ||
return [...result, brokenWords.map(line => line.join(" ")).join("\n")]; | ||
}, []); | ||
} | ||
console.warn("No canvas context was found, so the string was left as is!"); | ||
return text; | ||
return descriptors.map(({ text }) => text); | ||
} | ||
function toTextDescriptors(text, defaultFont) { | ||
if (isTextDescriptorArray(text)) { | ||
return text.map(({ text, font }) => ({ text, font: font || defaultFont })); | ||
} | ||
if (isStringArray(text)) { | ||
return text.map((member) => ({ text: member, font: defaultFont })); | ||
} | ||
return [{ text, font: defaultFont }]; | ||
} | ||
/** | ||
@@ -86,23 +68,11 @@ * Breaks a string into lines given a width and style for the text. | ||
function breakLinesEntry(text, width, font) { | ||
if (isArray(text)) { | ||
/* | ||
['hello there ', 'my good friend, ', 'how are you today?'] | ||
+ ['hello there my good\n friend, how are you\n today?'] | ||
= ['hello there ', 'my good\n friend, ', 'how are you\n today?'] | ||
*/ | ||
const withNewLines = breakLines(text.join(""), width, font); | ||
const newLinePositions = withNewLines | ||
.split("") | ||
.reduce((positions, char, i) => { | ||
if (char === "\n") { | ||
return [...positions, i]; | ||
} | ||
return positions; | ||
}, []); | ||
return newLinePositions.reduce((result, position) => { | ||
return insertNewlineAtPosition(position, result); | ||
}, text); | ||
const descriptors = toTextDescriptors(text, font); | ||
if (isStringArray(text)) { | ||
return breakLines(descriptors, width); | ||
} | ||
return breakLines(text, width, font); | ||
if (isTextDescriptorArray(text)) { | ||
return breakLines(descriptors, width); | ||
} | ||
return breakLines(descriptors, width)[0]; | ||
} | ||
export default breakLinesEntry; |
@@ -0,3 +1,8 @@ | ||
declare type TextDescriptor = { | ||
text: string; | ||
font?: string; | ||
}; | ||
declare function breakLinesEntry(text: string, width: number, font: string): string; | ||
declare function breakLinesEntry(text: string[], width: number, font: string): string[]; | ||
declare function breakLinesEntry(text: TextDescriptor[], width: number, font: string): string[]; | ||
export default breakLinesEntry; |
@@ -1,45 +0,8 @@ | ||
function isArray(text) { | ||
return Array.isArray(text); | ||
function isStringArray(text) { | ||
return Array.isArray(text) && text.every((member) => typeof member === 'string'); | ||
} | ||
function insertNewlineAtPosition(position, arrayOfStrings) { | ||
const { indexToInsertInto, localPosition } = arrayOfStrings.reduce(({ indexToInsertInto, localPosition, lengthOfPreceding }, string, i) => { | ||
const totalLength = string.length + lengthOfPreceding; | ||
if (!indexToInsertInto && !localPosition && position < totalLength) { | ||
return { | ||
indexToInsertInto: i, | ||
localPosition: position - lengthOfPreceding, | ||
lengthOfPreceding: totalLength | ||
}; | ||
} | ||
return { | ||
indexToInsertInto, | ||
localPosition, | ||
lengthOfPreceding: totalLength | ||
}; | ||
}, { indexToInsertInto: 0, localPosition: 0, lengthOfPreceding: 0 }); | ||
return (arrayOfStrings | ||
.map((string, i) => { | ||
if (i === indexToInsertInto) { | ||
return (string.slice(0, localPosition) + "\n" + string.slice(localPosition)); | ||
} | ||
return string; | ||
}) | ||
// This is to remove whitespace adjacent to newlines, but to preserve starting or trailing whitespace | ||
.map(string => string | ||
.split("\n") | ||
.map((str, i, strs) => { | ||
if (i < strs.length - 2 && i > 0) { | ||
return str.trim(); | ||
} | ||
if (i < strs.length - 2) { | ||
return str.trimRight(); | ||
} | ||
else if (i > 0) { | ||
return str.trimLeft(); | ||
} | ||
return str; | ||
}) | ||
.join("\n"))); | ||
function isTextDescriptorArray(text) { | ||
return Array.isArray(text) && !isStringArray(text); | ||
} | ||
function breakLines(text, width, font) { | ||
function breakLines(descriptors, width) { | ||
const supportsOffscreenCanvas = "OffscreenCanvas" in window; | ||
@@ -53,25 +16,44 @@ const canvasEl = document.createElement("canvas"); | ||
if (ctx) { | ||
ctx.font = font; | ||
const brokenWords = text.split(" ").reduce((accumulator, word) => { | ||
// get the last element of the accumulator | ||
const [lastLine] = accumulator.slice(-1); | ||
// add the word to it | ||
const maybeNextLine = [...lastLine, word].join(" "); | ||
// see if it fits within the width | ||
let { width: textWidth } = ctx.measureText(maybeNextLine); | ||
// if it does, append to the last element | ||
if (textWidth <= width) { | ||
return [...accumulator.slice(0, -1), [...lastLine, word]]; | ||
} | ||
if (lastLine.length === 0) { | ||
return [...accumulator.slice(0, -1), [word]]; | ||
} | ||
// if not, create a new array containing the word as the last element | ||
return [...accumulator, [word]]; | ||
}, [[]]); | ||
return brokenWords.map(line => line.join(" ")).join("\n"); | ||
return descriptors.reduce((result, { text, font }, descIndex) => { | ||
const lastLine = result[result.length - 1]; | ||
const lastLineSplit = lastLine ? lastLine.split('\n') : []; | ||
const runningLine = lastLineSplit.length > 0 ? lastLineSplit[lastLineSplit.length - 1] : ""; | ||
const prevFont = descriptors[descIndex - 1] ? descriptors[descIndex - 1].font : font; | ||
const brokenWords = text.split(" ").reduce((accumulator, word, i) => { | ||
// get the last element of the accumulator | ||
const [lastLine] = accumulator.slice(-1); | ||
// add the word to it | ||
const maybeNextLine = [...lastLine, word].join(" "); | ||
// see if it fits within the width | ||
ctx.font = prevFont; | ||
const { width: prevTextWidth } = ctx.measureText(runningLine); | ||
ctx.font = font; | ||
const { width: textWidth } = ctx.measureText(maybeNextLine); | ||
const totalWidth = i === 0 ? prevTextWidth + textWidth : textWidth; | ||
// if it does, append to the last element | ||
if (totalWidth <= width) { | ||
return [...accumulator.slice(0, -1), [...lastLine, word]]; | ||
} | ||
// Handle case of the first word being too long for a line | ||
if (totalWidth > width && i === 0 && runningLine === "") { | ||
return [...accumulator.slice(0, -1), [...lastLine, word]]; | ||
} | ||
// if not, create a new array containing the word as the last element | ||
return [...accumulator, [word]]; | ||
}, [[]]); | ||
return [...result, brokenWords.map(line => line.join(" ")).join("\n")]; | ||
}, []); | ||
} | ||
console.warn("No canvas context was found, so the string was left as is!"); | ||
return text; | ||
return descriptors.map(({ text }) => text); | ||
} | ||
function toTextDescriptors(text, defaultFont) { | ||
if (isTextDescriptorArray(text)) { | ||
return text.map(({ text, font }) => ({ text, font: font || defaultFont })); | ||
} | ||
if (isStringArray(text)) { | ||
return text.map((member) => ({ text: member, font: defaultFont })); | ||
} | ||
return [{ text, font: defaultFont }]; | ||
} | ||
/** | ||
@@ -86,22 +68,10 @@ * Breaks a string into lines given a width and style for the text. | ||
function breakLinesEntry(text, width, font) { | ||
if (isArray(text)) { | ||
/* | ||
['hello there ', 'my good friend, ', 'how are you today?'] | ||
+ ['hello there my good\n friend, how are you\n today?'] | ||
= ['hello there ', 'my good\n friend, ', 'how are you\n today?'] | ||
*/ | ||
const withNewLines = breakLines(text.join(""), width, font); | ||
const newLinePositions = withNewLines | ||
.split("") | ||
.reduce((positions, char, i) => { | ||
if (char === "\n") { | ||
return [...positions, i]; | ||
} | ||
return positions; | ||
}, []); | ||
return newLinePositions.reduce((result, position) => { | ||
return insertNewlineAtPosition(position, result); | ||
}, text); | ||
const descriptors = toTextDescriptors(text, font); | ||
if (isStringArray(text)) { | ||
return breakLines(descriptors, width); | ||
} | ||
return breakLines(text, width, font); | ||
if (isTextDescriptorArray(text)) { | ||
return breakLines(descriptors, width); | ||
} | ||
return breakLines(descriptors, width)[0]; | ||
} | ||
@@ -108,0 +78,0 @@ |
{ | ||
"name": "break-styled-lines", | ||
"description": "Add newlines to a string of text given a font style and width", | ||
"version": "1.1.2", | ||
"version": "1.2.0", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "files": [ |
# break-styled-lines | ||
This package is useful for when you want to know where linebreaks will occur in text given a font style (e.g. `16pt italic Georgia`) and the width of its container. Uses the Canvas API to measure text, so it's quite fast. Uses an OffscreenCanvas if the browser supports it. | ||
This package is useful for when you want to know where linebreaks will occur in text given a font style (e.g. `16pt italic Georgia`) and the width of its container. | ||
## Example | ||
This utility also supports some more complex use-cases: | ||
``` | ||
import breakLines from 'break-styled-lines'; | ||
- Adding linebreaks to a list of strings as though they were a single string, and returning the text back as arrays. | ||
- Adding linebreaks to a body of text which does not have consistent styling throughout (e.g. one much larger word in the middle of a sentence). | ||
Uses the Canvas API to measure text, so it's quite fast. Uses an OffscreenCanvas if the browser supports it. | ||
Comes with typescript types. | ||
## Basic example | ||
```ts | ||
import breakLines from "break-styled-lines"; | ||
const textWithLineBreaks = breakLines( | ||
@@ -36,11 +45,65 @@ "Good day to you my friends! What ails you on this day?", | ||
The single `breakLines` export supports three different cases: | ||
### Single string | ||
```ts | ||
breakLines( | ||
// The text you'd like to insert newlines into | ||
text: string | ||
// The width constraining the text | ||
width: number | ||
// The font style of the text (a value of the CSS font property e.g. 10px bold serif) | ||
style: string | ||
): string | string[] | ||
``` | ||
```ts | ||
breakLines("Good morrow my good man!", 100, "16pt serif"); | ||
``` | ||
### Array of strings | ||
```ts | ||
breakLines( | ||
// The string you'd like to break into lines. Can also be an array of strings, which will be treated as a single run of text, and then split back apart again. | ||
text: string | string[] | ||
// An array of strings, which will be treated as a single run of text, and then split back apart again before being returned. | ||
text: string[] | ||
// The width constraining the text | ||
width: number | ||
// The style of the text (a value of the CSS font property e.g. 10px bold serif) | ||
// The font style of the text (a value of the CSS font property e.g. 10px bold serif) | ||
style: string | ||
): string | string[] | ||
``` | ||
````ts | ||
breakLines(["Good morrow my good man!", " What brings you to our corner of the world?"], 100, "16pt serif") | ||
```ts | ||
### Text descriptors | ||
```ts | ||
breakLines( | ||
// An array of descriptors, which will be treated as a single run of text, and then split back apart again before being returned. | ||
// { text: string, font?: string } | ||
text: TextDescriptor[] | ||
// The width constraining the text | ||
width: number | ||
// The default font style of the text which will be used if the descriptor is not provided one (a value of the CSS font property e.g. 10px bold serif) | ||
style: string | ||
): string | string[] | ||
```` | ||
```ts | ||
breakLines( | ||
[ | ||
{ text: "Good morrow my good man!" }, | ||
{ text: " What brings you to our corner of the world?" }, | ||
{ | ||
text: " Our selection of the finest smoked cheeses, you say?!", | ||
font: "36pt bold Impact", | ||
}, | ||
], | ||
100, | ||
"16pt serif" | ||
); | ||
``` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
109
19214
163
1