@nrkn/text-layout
Version:
Wrapping and fitting styled runs of text
85 lines (65 loc) • 1.95 kB
text/typescript
import { splitRunsIntoLines } from './runs.js'
import {
Line, MeasureMetrics, MeasureRunAscent, MeasureRunBounds, MeasureRunWidth, TextRun
} from './types.js'
import { runsToWords } from './words.js'
// generates hard wrapped lines from runs
export const runsToLines = (
measureText: MeasureRunWidth | MeasureMetrics
) => {
const rtw = runsToWords(measureText)
const rtl = (runs: TextRun[]) => {
// handle hard breaks and split into words
const runLines = splitRunsIntoLines(runs)
const lines: Line[] = []
for (const rl of runLines) {
const words = rtw(rl)
const line: Line = {
words,
width: 0,
height: 0
}
for (const word of words) {
line.width += word.advanceX
line.height = Math.max(line.height, word.height)
}
const firstWord = words[0]
const lastWord = words[words.length - 1]
if (firstWord.opticalLeft !== undefined) {
line.opticalLeft = firstWord.opticalLeft
}
if (lastWord.opticalRight !== undefined) {
line.opticalRight = lastWord.opticalRight
}
lines.push(line)
}
return lines
}
return rtl
}
// find the maximum ascent of the words in a line
export const lineAscent = (measureAscent: MeasureRunAscent) =>
(line: Line) => {
let maxAscent = 0
for (const word of line.words) {
for (const run of word.runs) {
maxAscent = Math.max(maxAscent, measureAscent(run))
}
}
return maxAscent
}
export const opticalLineAscent = (line: Line) => {
let maxAscent: number | null = null
for (const word of line.words) {
for (const run of word.runs) {
if (run.actualBoundingBoxAscent !== undefined) {
if (maxAscent === null) {
maxAscent = run.actualBoundingBoxAscent
} else {
maxAscent = Math.max(maxAscent, run.actualBoundingBoxAscent)
}
}
}
}
return maxAscent
}