| |
| |
| |
|
|
| const { createCanvas } = require('canvas') |
| const { hexToRgb, normalizeColor } = require('./color') |
|
|
| |
| |
| |
| function renderText (layout, prepared, fontColor) { |
| const { lines, width, height } = layout |
| const { segments, fontSize, emojiSize } = prepared |
|
|
| if (lines.length === 0 || width === 0) { |
| return createCanvas(1, 1) |
| } |
|
|
| |
| const canvasWidth = Math.max(1, Math.ceil(width)) |
| const canvasHeight = Math.max(1, Math.ceil(height)) |
| const canvas = createCanvas(canvasWidth, canvasHeight) |
| const ctx = canvas.getContext('2d') |
|
|
| let currentFont = null |
| let currentFillStyle = null |
|
|
| for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) { |
| const line = lines[lineIdx] |
|
|
| for (const layoutSeg of line.segments) { |
| const seg = segments[layoutSeg.index] |
| if (!seg) continue |
|
|
| |
| let fillStyle = fontColor |
| if (seg.styles.includes('monospace')) fillStyle = '#5887a7' |
| else if (seg.styles.includes('mention')) fillStyle = '#6ab7ec' |
| else if (seg.styles.includes('spoiler')) { |
| const rgb = hexToRgb(normalizeColor(fontColor)) |
| fillStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.15)` |
| } |
|
|
| |
| if (currentFont !== seg.font) { |
| ctx.font = seg.font |
| currentFont = seg.font |
| } |
| if (currentFillStyle !== fillStyle) { |
| ctx.fillStyle = fillStyle |
| currentFillStyle = fillStyle |
| } |
|
|
| |
| let drawX = layoutSeg.x |
| if (line.direction === 'rtl') { |
| const segWidth = seg.kind === 'emoji' ? emojiSize : layoutSeg.width |
| drawX = width - layoutSeg.x - segWidth |
| } |
|
|
| const drawY = line.y |
|
|
| if (seg.kind === 'emoji' && seg.emojiImage) { |
| |
| ctx.drawImage( |
| seg.emojiImage, |
| drawX, |
| drawY - fontSize + (fontSize * 0.15), |
| emojiSize, |
| emojiSize |
| ) |
| } else if (seg.kind === 'space' || seg.kind === 'break') { |
| |
| } else { |
| |
| let drawText = seg.text |
| if (layoutSeg.graphemeStart !== undefined || layoutSeg.graphemeEnd !== undefined) { |
| if (prepared.computeGraphemeWidths) { |
| const gData = prepared.computeGraphemeWidths(seg) |
| if (gData) { |
| const start = layoutSeg.graphemeStart !== undefined ? layoutSeg.graphemeStart : 0 |
| const end = layoutSeg.graphemeEnd !== undefined ? layoutSeg.graphemeEnd : gData.texts.length |
| drawText = gData.texts.slice(start, end).join('') |
| } |
| } |
| } |
|
|
| ctx.fillText(drawText, drawX, drawY) |
|
|
| |
| if (seg.styles.includes('strikethrough')) { |
| const textWidth = ctx.measureText(drawText).width |
| ctx.fillRect(drawX, drawY - fontSize / 2.8, textWidth, fontSize * 0.1) |
| } |
|
|
| |
| if (seg.styles.includes('underline')) { |
| const textWidth = ctx.measureText(drawText).width |
| ctx.fillRect(drawX, drawY + 2, textWidth, fontSize * 0.1) |
| } |
| } |
| } |
|
|
| |
| if (lineIdx === lines.length - 1 && line.truncated) { |
| const ellipsis = '\u2026' |
| if (currentFillStyle !== fontColor) { |
| ctx.fillStyle = fontColor |
| currentFillStyle = fontColor |
| } |
|
|
| const lastSeg = line.segments[line.segments.length - 1] |
| if (lastSeg) { |
| let ellipsisX = lastSeg.x + lastSeg.width |
| |
| if (line.direction === 'rtl') { |
| const eWidth = ctx.measureText(ellipsis).width |
| ellipsisX = width - lastSeg.x - lastSeg.width - eWidth |
| } |
| ctx.fillText(ellipsis, ellipsisX, line.y) |
| } |
| } |
| } |
|
|
| return canvas |
| } |
|
|
| module.exports = { renderText } |
|
|