[fix] text to svg (#936)
* rewrite export text logic * Update tests * Update getTextSvgElement.ts * Update getTextSvgElement.ts * improve line breaking * labels + arrows * small offset for padding * Fix string bug
This commit is contained in:
parent
4285965fab
commit
11c3d1ba27
9 changed files with 262 additions and 66 deletions
|
@ -2073,8 +2073,8 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
)
|
||||
|
||||
// Clean up the SVG by removing any hidden elements
|
||||
svg.setAttribute('width', commonBounds.width.toString())
|
||||
svg.setAttribute('height', commonBounds.height.toString())
|
||||
svg.setAttribute('width', (commonBounds.width + SVG_EXPORT_PADDING * 2).toString())
|
||||
svg.setAttribute('height', (commonBounds.height + SVG_EXPORT_PADDING * 2).toString())
|
||||
|
||||
// Set export background
|
||||
const exportBackground: TDExportBackground = this.settings.exportBackground
|
||||
|
|
|
@ -966,8 +966,8 @@ Array [
|
|||
]
|
||||
`;
|
||||
|
||||
exports[`TldrawTestApp When copying to SVG Copies grouped shapes.: copied svg with group 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 232 232\\" width=\\"200\\" height=\\"200\\" style=\\"background-color: transparent;\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block');</style></defs><g/></svg>"`;
|
||||
exports[`TldrawTestApp When copying to SVG Copies grouped shapes.: copied svg with group 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 232 232\\" width=\\"232\\" height=\\"232\\" style=\\"background-color: transparent;\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block');</style></defs><g/></svg>"`;
|
||||
|
||||
exports[`TldrawTestApp When copying to SVG Copies shapes.: copied svg 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 236.74 236.74\\" width=\\"204.74\\" height=\\"204.74\\" style=\\"background-color: transparent;\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block');</style></defs></svg>"`;
|
||||
exports[`TldrawTestApp When copying to SVG Copies shapes.: copied svg 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 236.74 236.74\\" width=\\"236.74\\" height=\\"236.74\\" style=\\"background-color: transparent;\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block');</style></defs></svg>"`;
|
||||
|
||||
exports[`TldrawTestApp When copying to SVG Respects child index: copied svg with reordered elements 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 232 232\\" width=\\"200\\" height=\\"200\\" style=\\"background-color: transparent;\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block');</style></defs></svg>"`;
|
||||
exports[`TldrawTestApp When copying to SVG Respects child index: copied svg with reordered elements 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 232 232\\" width=\\"232\\" height=\\"232\\" style=\\"background-color: transparent;\\"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block');</style></defs></svg>"`;
|
||||
|
|
|
@ -12,12 +12,23 @@ import {
|
|||
LabelMask,
|
||||
TextLabel,
|
||||
defaultStyle,
|
||||
getFontFace,
|
||||
getFontSize,
|
||||
getFontStyle,
|
||||
getShapeStyle,
|
||||
getTextLabelSize,
|
||||
getTextSvgElement,
|
||||
} from '~state/shapes/shared'
|
||||
import { styled } from '~styles'
|
||||
import { ArrowShape, DashStyle, Decoration, TDMeta, TDShapeType, TransformInfo } from '~types'
|
||||
import {
|
||||
AlignStyle,
|
||||
ArrowShape,
|
||||
DashStyle,
|
||||
Decoration,
|
||||
TDMeta,
|
||||
TDShapeType,
|
||||
TransformInfo,
|
||||
} from '~types'
|
||||
import {
|
||||
getArcLength,
|
||||
getArcPoints,
|
||||
|
@ -514,6 +525,64 @@ export class ArrowUtil extends TDShapeUtil<T, E> {
|
|||
|
||||
return nextShape
|
||||
}
|
||||
|
||||
getSvgElement = (shape: ArrowShape, isDarkMode: boolean): SVGElement | void => {
|
||||
const elm = document.getElementById(shape.id + '_svg')?.cloneNode(true) as SVGElement
|
||||
if (!elm) return // possibly in test mode
|
||||
const hasLabel = shape.label?.trim()?.length ?? 0 > 0
|
||||
if (hasLabel) {
|
||||
const s = shape as ArrowShape
|
||||
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
||||
|
||||
const font = getFontStyle(shape.style)
|
||||
const labelSize = getTextLabelSize(shape.label!, font)
|
||||
const fontSize = getFontSize(shape.style.size, shape.style.font) * (shape.style.scale ?? 1)
|
||||
const fontFamily = getFontFace(shape.style.font).slice(1, -1)
|
||||
|
||||
const labelElm = getTextSvgElement(
|
||||
s.label!,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
AlignStyle.Start,
|
||||
labelSize[0],
|
||||
false
|
||||
)
|
||||
|
||||
let dist: number
|
||||
|
||||
const { start, bend, end } = shape.handles
|
||||
const isStraightLine = Vec.dist(bend.point, Vec.toFixed(Vec.med(start.point, end.point))) < 1
|
||||
if (isStraightLine) {
|
||||
dist = Vec.dist(start.point, end.point)
|
||||
} else {
|
||||
const circle = getCtp(start.point, bend.point, end.point)
|
||||
const center = circle.slice(0, 2)
|
||||
const radius = circle[2]
|
||||
const length = getArcLength(center, radius, start.point, end.point)
|
||||
dist = Math.abs(length)
|
||||
}
|
||||
|
||||
const scale = Math.max(
|
||||
0.5,
|
||||
Math.min(1, Math.max(dist / (labelSize[1] + 128), dist / (labelSize[0] + 128)))
|
||||
)
|
||||
const bounds = this.getBounds(shape)
|
||||
|
||||
const offset = Vec.sub(shape.handles.bend.point, [bounds.width / 2, bounds.height / 2])
|
||||
const x = bounds.width / 2 - (labelSize[0] / 2) * scale + offset[0]
|
||||
const y = bounds.height / 2 - (labelSize[1] / 2) * scale + offset[1]
|
||||
|
||||
labelElm.setAttribute('transform', `translate(${x}, ${y})`)
|
||||
labelElm.setAttribute('fill', getShapeStyle(shape.style, isDarkMode).stroke)
|
||||
labelElm.setAttribute('transform-origin', 'center center')
|
||||
g.setAttribute('text-align', 'center')
|
||||
g.setAttribute('text-anchor', 'middle')
|
||||
g.appendChild(elm)
|
||||
g.appendChild(labelElm)
|
||||
return g
|
||||
}
|
||||
return elm
|
||||
}
|
||||
}
|
||||
|
||||
const FullWrapper = styled('div', { width: '100%', height: '100%' })
|
||||
|
|
|
@ -2,13 +2,15 @@ import { HTMLContainer, TLBounds, Utils } from '@tldraw/core'
|
|||
import { Vec } from '@tldraw/vec'
|
||||
import * as React from 'react'
|
||||
import { stopPropagation } from '~components/stopPropagation'
|
||||
import { GHOSTED_OPACITY } from '~constants'
|
||||
import { GHOSTED_OPACITY, LETTER_SPACING } from '~constants'
|
||||
import { TLDR } from '~state/TLDR'
|
||||
import { TDShapeUtil } from '~state/shapes/TDShapeUtil'
|
||||
import {
|
||||
TextAreaUtils,
|
||||
defaultTextStyle,
|
||||
getBoundsRectangle,
|
||||
getFontFace,
|
||||
getStickyFontSize,
|
||||
getStickyFontStyle,
|
||||
getStickyShapeStyle,
|
||||
getTextSvgElement,
|
||||
|
@ -286,9 +288,22 @@ export class StickyUtil extends TDShapeUtil<T, E> {
|
|||
|
||||
getSvgElement = (shape: T, isDarkMode: boolean): SVGElement | void => {
|
||||
const bounds = this.getBounds(shape)
|
||||
const textBounds = Utils.expandBounds(bounds, -PADDING)
|
||||
const textElm = getTextSvgElement(shape.text, shape.style, textBounds)
|
||||
|
||||
const style = getStickyShapeStyle(shape.style, isDarkMode)
|
||||
|
||||
const fontSize = getStickyFontSize(shape.style.size) * (shape.style.scale ?? 1)
|
||||
const fontFamily = getFontFace(shape.style.font).slice(1, -1)
|
||||
const textAlign = shape.style.textAlign ?? AlignStyle.Start
|
||||
|
||||
const textElm = getTextSvgElement(
|
||||
shape.text,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
textAlign,
|
||||
bounds.width - PADDING * 2,
|
||||
true
|
||||
)
|
||||
|
||||
textElm.setAttribute('fill', style.color)
|
||||
textElm.setAttribute('transform', `translate(${PADDING}, ${PADDING})`)
|
||||
|
||||
|
@ -345,6 +360,7 @@ const StyledStickyContainer = styled('div', {
|
|||
const commonTextWrapping = {
|
||||
whiteSpace: 'pre-wrap',
|
||||
overflowWrap: 'break-word',
|
||||
letterSpacing: LETTER_SPACING,
|
||||
}
|
||||
|
||||
const StyledText = styled('div', {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Vec } from '@tldraw/vec'
|
|||
import * as React from 'react'
|
||||
import { BINDING_DISTANCE } from '~constants'
|
||||
import { AlignStyle, ShapesWithProp, TDBinding, TDMeta, TDShape, TransformInfo } from '~types'
|
||||
import { getFontStyle, getShapeStyle } from './shared'
|
||||
import { getFontFace, getFontSize, getFontStyle, getShapeStyle } from './shared'
|
||||
import { getTextLabelSize } from './shared/getTextSize'
|
||||
import { getTextSvgElement } from './shared/getTextSvgElement'
|
||||
|
||||
|
@ -178,17 +178,33 @@ export abstract class TDShapeUtil<T extends TDShape, E extends Element = any> ex
|
|||
getSvgElement = (shape: T, isDarkMode: boolean): SVGElement | void => {
|
||||
const elm = document.getElementById(shape.id + '_svg')?.cloneNode(true) as SVGElement
|
||||
if (!elm) return // possibly in test mode
|
||||
if ('label' in shape && (shape as any).label) {
|
||||
const hasLabel = shape.label?.trim()?.length ?? 0 > 0
|
||||
if (hasLabel) {
|
||||
const s = shape as TDShape & { label: string }
|
||||
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
||||
const bounds = this.getBounds(shape)
|
||||
|
||||
const font = getFontStyle(shape.style)
|
||||
const labelSize = getTextLabelSize(shape.label!, font)
|
||||
const fontSize = getFontSize(shape.style.size, shape.style.font) * (shape.style.scale ?? 1)
|
||||
const fontFamily = getFontFace(shape.style.font).slice(1, -1)
|
||||
|
||||
const labelElm = getTextSvgElement(
|
||||
s['label'],
|
||||
{ ...shape.style, textAlign: AlignStyle.Start },
|
||||
bounds
|
||||
fontSize,
|
||||
fontFamily,
|
||||
AlignStyle.Start,
|
||||
labelSize[0],
|
||||
false
|
||||
)
|
||||
|
||||
const bounds = this.getBounds(shape)
|
||||
|
||||
labelElm.setAttribute(
|
||||
'transform',
|
||||
`translate(${bounds.width / 2 - labelSize[0] / 2}, ${bounds.height / 2 - labelSize[1] / 2})`
|
||||
)
|
||||
labelElm.setAttribute('fill', getShapeStyle(shape.style, isDarkMode).stroke)
|
||||
labelElm.setAttribute('transform-origin', 'top left')
|
||||
labelElm.setAttribute('transform-origin', 'center center')
|
||||
g.setAttribute('text-align', 'center')
|
||||
g.setAttribute('text-anchor', 'middle')
|
||||
g.appendChild(elm)
|
||||
|
|
|
@ -8,6 +8,8 @@ import { TDShapeUtil } from '~state/shapes/TDShapeUtil'
|
|||
import {
|
||||
TextAreaUtils,
|
||||
defaultTextStyle,
|
||||
getFontFace,
|
||||
getFontSize,
|
||||
getFontStyle,
|
||||
getShapeStyle,
|
||||
getTextAlign,
|
||||
|
@ -389,10 +391,23 @@ export class TextUtil extends TDShapeUtil<T, E> {
|
|||
getSvgElement = (shape: T, isDarkMode: boolean): SVGElement | void => {
|
||||
const bounds = this.getBounds(shape)
|
||||
const style = getShapeStyle(shape.style, isDarkMode)
|
||||
const elm = getTextSvgElement(shape.text, shape.style, bounds)
|
||||
elm.setAttribute('fill', style.stroke)
|
||||
|
||||
return elm
|
||||
const fontSize = getFontSize(shape.style.size, shape.style.font) * (shape.style.scale ?? 1)
|
||||
const fontFamily = getFontFace(shape.style.font).slice(1, -1)
|
||||
const textAlign = shape.style.textAlign ?? AlignStyle.Start
|
||||
|
||||
const textElm = getTextSvgElement(
|
||||
shape.text,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
textAlign,
|
||||
bounds.width,
|
||||
false
|
||||
)
|
||||
|
||||
textElm.setAttribute('fill', style.stroke)
|
||||
|
||||
return textElm
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -211,6 +211,7 @@ const TextWrapper = styled('div', {
|
|||
const commonTextWrapping = {
|
||||
whiteSpace: 'pre-wrap',
|
||||
overflowWrap: 'break-word',
|
||||
letterSpacing: LETTER_SPACING,
|
||||
}
|
||||
|
||||
const InnerWrapper = styled('div', {
|
||||
|
@ -220,7 +221,6 @@ const InnerWrapper = styled('div', {
|
|||
minHeight: 1,
|
||||
minWidth: 1,
|
||||
lineHeight: 1,
|
||||
letterSpacing: LETTER_SPACING,
|
||||
outline: 0,
|
||||
fontWeight: '500',
|
||||
textAlign: 'center',
|
||||
|
@ -265,7 +265,6 @@ const TextArea = styled('textarea', {
|
|||
minHeight: 'inherit',
|
||||
minWidth: 'inherit',
|
||||
lineHeight: 'inherit',
|
||||
letterSpacing: 'inherit',
|
||||
outline: 0,
|
||||
fontWeight: 'inherit',
|
||||
overflow: 'hidden',
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { LETTER_SPACING } from '~constants'
|
||||
import { FontStyle } from '~types'
|
||||
|
||||
let melm: any
|
||||
|
||||
|
|
|
@ -1,66 +1,146 @@
|
|||
import type { TLBounds } from '@tldraw/core'
|
||||
import { LINE_HEIGHT } from '~constants'
|
||||
import { TLBounds } from '@tldraw/core'
|
||||
import { LETTER_SPACING, LINE_HEIGHT } from '~constants'
|
||||
import { AlignStyle, ShapeStyles } from '~types'
|
||||
import { getTextAlign } from './getTextAlign'
|
||||
import { getTextLabelSize } from './getTextSize'
|
||||
import { getFontFace, getFontSize, getFontStyle } from './shape-styles'
|
||||
|
||||
export function getTextSvgElement(text: string, style: ShapeStyles, bounds: TLBounds) {
|
||||
const fontSize = getFontSize(style.size, style.font)
|
||||
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
||||
const scale = style.scale ?? 1
|
||||
// https://drafts.csswg.org/css-text/#word-separator
|
||||
// split on any of these characters
|
||||
const wordSeparator = new RegExp(
|
||||
`${[0x0020, 0x00a0, 0x1361, 0x10100, 0x10101, 0x1039, 0x1091]
|
||||
.map((c) => String.fromCodePoint(c))
|
||||
.join('|')}`
|
||||
)
|
||||
|
||||
const font = getFontStyle(style)
|
||||
const [, height] = getTextLabelSize(text, font)
|
||||
export function getTextSvgElement(
|
||||
text: string,
|
||||
fontSize: number,
|
||||
fontFamily: string,
|
||||
textAlign: AlignStyle,
|
||||
width: number,
|
||||
wrap = false
|
||||
) {
|
||||
const fontWeight = 'normal'
|
||||
const lineHeight = 1
|
||||
const letterSpacingPct = LETTER_SPACING
|
||||
|
||||
const textLines = text.split('\n').map((line, i) => {
|
||||
const textElm = document.createElementNS('http://www.w3.org/2000/svg', 'text')
|
||||
textElm.textContent = line
|
||||
// Collect lines
|
||||
|
||||
textElm.setAttribute('y', fontSize * (0.5 + i * LINE_HEIGHT) + '')
|
||||
textElm.setAttribute('letter-spacing', fontSize * -0.03 + '')
|
||||
textElm.setAttribute('font-size', fontSize + 'px')
|
||||
textElm.setAttribute('font-family', getFontFace(style.font).slice(1, -1))
|
||||
textElm.setAttribute('text-align', getTextAlign(style.textAlign))
|
||||
textElm.setAttribute('text-align', getTextAlign(style.textAlign))
|
||||
textElm.setAttribute('alignment-baseline', 'central')
|
||||
|
||||
const [width] = getTextLabelSize(line, font)
|
||||
|
||||
console.log(font, scale, width, bounds.width)
|
||||
|
||||
textElm.setAttribute(
|
||||
'transform',
|
||||
`translate(${(bounds.width - width) / 2}, ${(bounds.height - height * scale) / 2})`
|
||||
)
|
||||
if (style.scale !== 1) {
|
||||
textElm.setAttribute('transform', `scale(${style.scale})`)
|
||||
}
|
||||
g.appendChild(textElm)
|
||||
|
||||
return textElm
|
||||
const lines = breakText({
|
||||
text,
|
||||
wrap,
|
||||
width,
|
||||
fontSize,
|
||||
fontWeight,
|
||||
fontFamily,
|
||||
fontStyle: 'normal',
|
||||
textAlign: 'left',
|
||||
letterSpacing: LETTER_SPACING,
|
||||
lineHeight: 1,
|
||||
})
|
||||
|
||||
switch (style.textAlign) {
|
||||
const textElm = document.createElementNS('http://www.w3.org/2000/svg', 'text')
|
||||
textElm.setAttribute('font-size', fontSize + 'px')
|
||||
textElm.setAttribute('font-family', fontFamily)
|
||||
textElm.setAttribute('font-weight', fontWeight)
|
||||
textElm.setAttribute('line-height', lineHeight * fontSize + 'px')
|
||||
textElm.setAttribute('letter-spacing', letterSpacingPct)
|
||||
textElm.setAttribute('text-align', textAlign ?? 'left')
|
||||
textElm.setAttribute('dominant-baseline', 'mathematical')
|
||||
textElm.setAttribute('alignment-baseline', 'mathematical')
|
||||
|
||||
const textLines = lines.map((line, i) => {
|
||||
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan')
|
||||
tspan.textContent = line + '\n'
|
||||
tspan.setAttribute('y', lineHeight * fontSize * (i + 0.5) + 'px')
|
||||
textElm.appendChild(tspan)
|
||||
return tspan
|
||||
})
|
||||
|
||||
switch (textAlign) {
|
||||
case AlignStyle.Middle: {
|
||||
g.setAttribute('text-align', 'center')
|
||||
g.setAttribute('text-anchor', 'middle')
|
||||
textLines.forEach((textElm) => {
|
||||
textElm.setAttribute('x', bounds.width / 2 / scale + '')
|
||||
})
|
||||
textElm.setAttribute('text-align', 'center')
|
||||
textElm.setAttribute('text-anchor', 'middle')
|
||||
textLines.forEach((textElm) => textElm.setAttribute('x', 4 + width / 2 + ''))
|
||||
break
|
||||
}
|
||||
case AlignStyle.End: {
|
||||
g.setAttribute('text-align', 'right')
|
||||
g.setAttribute('text-anchor', 'end')
|
||||
textLines.forEach((textElm) => textElm.setAttribute('x', bounds.width / scale + ''))
|
||||
textElm.setAttribute('text-align', 'right')
|
||||
textElm.setAttribute('text-anchor', 'end')
|
||||
textLines.forEach((textElm) => textElm.setAttribute('x', 4 + width + ''))
|
||||
break
|
||||
}
|
||||
case AlignStyle.Start: {
|
||||
g.setAttribute('text-align', 'left')
|
||||
g.setAttribute('text-anchor', 'start')
|
||||
default: {
|
||||
textElm.setAttribute('text-align', 'left')
|
||||
textElm.setAttribute('text-anchor', 'start')
|
||||
textLines.forEach((textElm) => textElm.setAttribute('x', '4'))
|
||||
}
|
||||
}
|
||||
|
||||
return g
|
||||
return textElm
|
||||
}
|
||||
|
||||
function breakText(opts: {
|
||||
text: string
|
||||
wrap: boolean
|
||||
width: number
|
||||
fontSize: number
|
||||
fontWeight: string
|
||||
fontFamily: string
|
||||
fontStyle: string
|
||||
lineHeight: number
|
||||
letterSpacing: string
|
||||
textAlign: string
|
||||
}): string[] {
|
||||
const textElm = document.createElement('div')
|
||||
textElm.style.setProperty('position', 'absolute')
|
||||
textElm.style.setProperty('top', '-9999px')
|
||||
textElm.style.setProperty('left', '-9999px')
|
||||
textElm.style.setProperty('width', opts.width + 'px')
|
||||
textElm.style.setProperty('height', 'min-content')
|
||||
textElm.style.setProperty('font-size', opts.fontSize + 'px')
|
||||
textElm.style.setProperty('font-family', opts.fontFamily)
|
||||
textElm.style.setProperty('font-weight', opts.fontWeight)
|
||||
textElm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
|
||||
textElm.style.setProperty('letter-spacing', opts.letterSpacing)
|
||||
textElm.style.setProperty('text-align', opts.textAlign)
|
||||
document.body.appendChild(textElm)
|
||||
|
||||
// Collect lines
|
||||
|
||||
// Split the text into words
|
||||
const words = opts.text
|
||||
.split(wordSeparator)
|
||||
.flatMap((word) => word.replace('\n', ' \n'))
|
||||
.join(' ')
|
||||
.split(' ')
|
||||
|
||||
// Iterate through the words looking for either line breaks, or
|
||||
// when the measured line exceeds the width of the container (minus
|
||||
// its padding); at either point, create a new line.
|
||||
|
||||
textElm.innerText = words[0]
|
||||
let prevHeight = textElm.offsetHeight
|
||||
|
||||
let currentLine = [words[0]]
|
||||
const lines: string[][] = [currentLine]
|
||||
|
||||
for (let i = 1; i < words.length; i++) {
|
||||
const word = words[i]
|
||||
textElm.innerText += ' ' + word
|
||||
const newHeight = textElm.offsetHeight
|
||||
if (newHeight > prevHeight) {
|
||||
prevHeight = newHeight
|
||||
currentLine = []
|
||||
lines.push(currentLine)
|
||||
}
|
||||
|
||||
// Push the current word to the current line
|
||||
currentLine.push(word)
|
||||
}
|
||||
|
||||
textElm.remove()
|
||||
|
||||
return lines.map((line) => line.join(' '))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue