UNPKG

react-native-skia-responsive-text

Version:

A library providing a wrapper for the React Native Skia Text component, offering features like user-friendly text alignment, efficient multiline text management, and straightforward text truncation.

193 lines (189 loc) 7.5 kB
import { ELLIPSIS } from '../constants'; import { trimLineCenter, trimLineEnd, trimLineStart } from './ellipsize'; import { mergeLines } from './text'; const wrapWithoutTrimmingFromStart = function (chunks, font, width) { let maxLines = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Infinity; if (maxLines <= 0) return []; const result = []; let currentLine = { chunks: [], width: 0 }; let nextChunkWidth = font.getTextWidth(chunks[0]); const addNewLine = () => { result.push({ text: currentLine.chunks.join(''), width: currentLine.width }); currentLine = { chunks: [], width: 0 }; }; const shouldBreak = () => result.length === maxLines; for (let i = 0; i < chunks.length; i++) { if (shouldBreak()) break; const chunk = chunks[i]; const chunkWidth = nextChunkWidth; const nextChunk = chunks[i + 1]; nextChunkWidth = nextChunk ? font.getTextWidth(nextChunk) : Infinity; // If the chunk will fit in the text line, don't break it if (chunkWidth <= width) { // If the current chunk won't fit in the current line, add it to the next line if (currentLine.width + chunkWidth > width) { addNewLine(); if (result.length === maxLines) { break; } } // Add the chunk to the current line currentLine.chunks.push(chunk); currentLine.width += chunkWidth; } // Otherwise, if the chunk is a single word, break it else { if (currentLine.chunks.length && result.length < maxLines - 1) { addNewLine(); } let j = 0; let k = 1; let nextSliceWidth = font.getTextWidth(chunk.slice(j, k)); for (; k <= chunk.length; k++) { const slice = chunk.slice(j, k); if (currentLine.width + nextSliceWidth > width || k === chunk.length) { currentLine.chunks.push(slice); currentLine.width += font.getTextWidth(slice); if (k < chunk.length) addNewLine(); if (shouldBreak()) break; j = k; } nextSliceWidth = font.getTextWidth(chunk.slice(j, k + 1)); } } } // If the last line is not empty, add it to the result if (currentLine.chunks.length && !shouldBreak()) { addNewLine(); } return result; }; const wrapWithoutTrimmingFromEnd = function (chunks, font, width) { let maxLines = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Infinity; if (maxLines <= 0) return []; const result = []; let currentLine = { chunks: [], width: 0 }; let nextChunkWidth = font.getTextWidth(chunks[chunks.length - 1]); const addNewLine = () => { result.push({ text: currentLine.chunks.reverse().join(''), width: currentLine.width }); currentLine = { chunks: [], width: 0 }; }; const shouldBreak = () => result.length === maxLines; for (let i = chunks.length - 1; i >= 0; i--) { if (shouldBreak()) break; const chunk = chunks[i]; const chunkWidth = nextChunkWidth; const nextChunk = chunks[i - 1]; nextChunkWidth = nextChunk ? font.getTextWidth(nextChunk) : Infinity; // If the chunk will fit in the text line, don't break it if (chunkWidth <= width) { // If the current chunk won't fit in the current line, add it to the next line if (chunkWidth + currentLine.width > width) { addNewLine(); if (result.length === maxLines) { break; } } // Add the chunk to the current line currentLine.chunks.push(chunk); currentLine.width += chunkWidth; } // Otherwise, if the chunk is a single word, break it else { if (currentLine.chunks.length && result.length < maxLines - 1) { addNewLine(); } let j = chunk.length; let k = j - 1; let nextSliceWidth = font.getTextWidth(chunk.slice(k, j)); for (; k >= 0; k--) { const slice = chunk.slice(k, j); if (currentLine.width + nextSliceWidth > width || k === 0) { currentLine.chunks.push(slice); currentLine.width += font.getTextWidth(slice); if (k > 0) addNewLine(); if (shouldBreak()) break; j = k; } nextSliceWidth = font.getTextWidth(chunk.slice(k - 1, j)); } } } // If the last line is not empty, add it to the result if (currentLine.chunks.length && !shouldBreak()) { addNewLine(); } return result.reverse(); }; const wrapWithTailTrimming = function (chunks, font, width, numberOfLines) { let suffix = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : ELLIPSIS; const withoutTrimming = wrapWithoutTrimmingFromStart(chunks, font, width, numberOfLines + 1); if (withoutTrimming.length <= numberOfLines) { return withoutTrimming; } return [...withoutTrimming.slice(0, numberOfLines - 1), trimLineEnd(mergeLines(withoutTrimming.slice(numberOfLines - 1, numberOfLines + 1)), font, width, suffix)]; }; const wrapWithClipTrimming = (chunks, font, width, numberOfLines) => wrapWithTailTrimming(chunks, font, width, numberOfLines, ''); const wrapWithHeadTrimming = (chunks, font, width, numberOfLines) => { const withoutTrimming = wrapWithoutTrimmingFromEnd(chunks, font, width, numberOfLines + 1); // If trimming is not needed, return the result of wrapWithoutTrimmingFromStart // (to make it look more natural) if (withoutTrimming.length <= numberOfLines) { return wrapWithoutTrimmingFromStart(chunks, font, width, numberOfLines); } return [trimLineStart(mergeLines(withoutTrimming.slice(0, 2)), font, width), ...withoutTrimming.slice(2)]; }; const wrapWithMiddleTrimming = (chunks, font, width, numberOfLines) => { const withoutFromStart = wrapWithoutTrimmingFromStart(chunks, font, width, numberOfLines + 1); // If trimming is not needed, return the result of wrapWithoutTrimmingFromStart // (to make it look more natural) if (withoutFromStart.length <= numberOfLines) { return withoutFromStart; } const halfLines = Math.floor(numberOfLines / 2); const withoutFromEnd = wrapWithoutTrimmingFromEnd(chunks, font, width, halfLines + 1); if (numberOfLines % 2 === 0) { return [...withoutFromStart.slice(0, halfLines - 1), trimLineEnd(mergeLines(withoutFromStart.slice(halfLines - 1, halfLines + 1)), font, width), trimLineStart(mergeLines(withoutFromEnd.slice(0, 2)), font, width), ...withoutFromEnd.slice(2)]; } return [...withoutFromStart.slice(0, halfLines), trimLineCenter(mergeLines([withoutFromStart[halfLines], withoutFromEnd[0]]), font, width), ...withoutFromEnd.slice(1)]; }; export const wrapText = function (textChunks, font, width) { let numberOfLines = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Infinity; let ellipsizeMode = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 'tail'; if (!textChunks.length) { return []; } if (numberOfLines === Infinity) { return wrapWithoutTrimmingFromStart(textChunks, font, width); } switch (ellipsizeMode) { case 'head': return wrapWithHeadTrimming(textChunks, font, width, numberOfLines); case 'clip': return wrapWithClipTrimming(textChunks, font, width, numberOfLines); case 'middle': return wrapWithMiddleTrimming(textChunks, font, width, numberOfLines); case 'tail': default: return wrapWithTailTrimming(textChunks, font, width, numberOfLines); } }; //# sourceMappingURL=wrapping.js.map