From 9ec0029fb4030a31bf4d47169cd879190f410cb8 Mon Sep 17 00:00:00 2001 From: Maikel Brons Date: Sun, 24 Aug 2025 12:34:11 +0200 Subject: [PATCH] BBcode handling, currently supported color, underline, strikethrough --- examples/tests/text-bbcode.ts | 206 ++++++++++ src/core/shaders/webgl/SdfShader.ts | 26 +- src/core/text-rendering/CanvasFontHandler.ts | 15 +- src/core/text-rendering/SdfTextRenderer.ts | 367 +++++++++++++++--- src/core/text-rendering/TextRenderer.ts | 12 + src/core/text-rendering/sdf/Utils.ts | 181 +++++++++ .../chromium-ci/text-bbcode-1.png | Bin 0 -> 64379 bytes 7 files changed, 750 insertions(+), 57 deletions(-) create mode 100644 examples/tests/text-bbcode.ts create mode 100644 visual-regression/certified-snapshots/chromium-ci/text-bbcode-1.png diff --git a/examples/tests/text-bbcode.ts b/examples/tests/text-bbcode.ts new file mode 100644 index 00000000..dbf47c15 --- /dev/null +++ b/examples/tests/text-bbcode.ts @@ -0,0 +1,206 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2025 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ExampleSettings } from '../common/ExampleSettings.js'; + +export async function automation(settings: ExampleSettings) { + await test(settings); + await settings.snapshot(); +} + +export default async function test({ renderer, testRoot }: ExampleSettings) { + const fontSize = 28; + const fontFamily = 'Ubuntu'; + + // Main container + const view = renderer.createNode({ + x: 0, + y: 0, + w: 1250, + h: 600, + color: 0xf8f8f8ff, + parent: testRoot, + }); + + // Title + renderer.createTextNode({ + text: 'BBCode Formatting Test - SDF Renderer', + x: 20, + y: 30, + fontSize: 24, + fontFamily, + color: 0x000000ff, + parent: view, + }); + + // Subtitle + renderer.createTextNode({ + text: 'Demonstrating color and style BBCode tags', + x: 20, + y: 60, + fontSize: 16, + fontFamily, + color: 0x666666ff, + parent: view, + }); + + const examples = [ + { + title: 'Color Tags', + text: '[color=red]Red text[/color] [u]and[/u] [color=#00FF00][u]green[/u] text[/color]', + description: 'Named and hex colors', + }, + { + title: 'Style Tags', + text: '[b]Bold[/b] [i]Italic[/i] [u]Underline[/u] text', + description: 'Bold, italic, underline', + }, + { + title: 'Strikethrough', + text: '[s]Strikethrough text[/s] and normal text', + description: 'Strikethrough style', + }, + { + title: 'Nested Tags', + text: '[color=purple][u]Purple Bold[/u][/color] [color=orange][s]Orange Italic[/s][/color]', + description: 'Combined formatting', + }, + { + title: 'Mixed Decorations', + text: '[u][s]Under[color=red]lin[/color]e[/s][/u] and [s]strikethrough[/s] text', + description: 'Different text decorations', + }, + { + title: 'Complex Mix', + text: 'Hello [color=red]World[/color] [color=blue][s]Strike[/s][/color] [u]Underline[/u]!', + description: 'Multiple mixed tags', + }, + ]; + + examples.forEach((example, index) => { + const rowY = 80 + index * 50; + + // Create container for BBCode text + const textContainer = renderer.createNode({ + x: 30, + y: rowY, + w: 600, + h: 45, + color: 0x0066cc10, + parent: view, + }); + + // Add a subtle border + renderer.createNode({ + x: 0, + y: 0, + color: 0x0066cc40, + w: textContainer.w, + h: 2, + parent: textContainer, + }); + renderer.createNode({ + x: 0, + y: textContainer.h - 2, + w: textContainer.w, + h: 2, + color: 0x0066cc40, + parent: textContainer, + }); + + // Title label + renderer.createTextNode({ + text: example.title, + x: 650, + y: rowY, + fontSize: 14, + fontFamily, + fontStyle: 'normal', + color: 0x0066ccff, + parent: view, + }); + + // Description label + renderer.createTextNode({ + text: example.description, + x: 650, + y: rowY + 25, + fontSize: 12, + fontFamily, + color: 0x888888ff, + parent: view, + }); + + // BBCode formatted text + renderer.createTextNode({ + text: example.text, + x: 10, + y: 10, + fontSize, + fontFamily, + textRendererOverride: 'sdf', + color: 0x000000ff, // Default text color + parent: textContainer, + }); + + // // Raw BBCode display (what the user typed) + renderer.createTextNode({ + text: `Raw: ${example.text}`, + x: 850, + y: rowY + 45, + fontSize: 10, + fontFamily: 'monospace', + color: 0x666666ff, + maxWidth: 350, + parent: view, + }); + }); + + // Legend section + const legendY = 80 + examples.length * 50 + 20; + + renderer.createTextNode({ + text: 'Supported BBCode Tags:', + x: 30, + y: legendY, + fontSize: 18, + fontFamily, + color: 0x000000ff, + parent: view, + }); + + const tags = [ + '[color=name] or [color=#hex] - Text color', + '[b] - Bold text (TODO)', + '[i] - Italic text (TODO)', + '[u] - Underlined text', + '[s] - Strikethrough text', + ]; + + tags.forEach((tag, index) => { + renderer.createTextNode({ + text: `* ${tag}`, + x: 50, + y: legendY + 20 + index * 22, + fontSize: 15, + color: 0x000000ff, + parent: view, + }); + }); +} diff --git a/src/core/shaders/webgl/SdfShader.ts b/src/core/shaders/webgl/SdfShader.ts index 836230dd..1a0db983 100644 --- a/src/core/shaders/webgl/SdfShader.ts +++ b/src/core/shaders/webgl/SdfShader.ts @@ -77,6 +77,7 @@ export const Sdf: WebGlShaderType = { // It will receive data from a buffer attribute vec2 a_position; attribute vec2 a_textureCoords; + attribute vec4 a_color; uniform vec2 u_resolution; uniform mat3 u_transform; @@ -85,6 +86,7 @@ export const Sdf: WebGlShaderType = { uniform float u_size; varying vec2 v_texcoord; + varying vec4 v_color; void main() { vec2 scrolledPosition = a_position * u_size - vec2(0, u_scrollY); @@ -95,6 +97,7 @@ export const Sdf: WebGlShaderType = { gl_Position = vec4(screenSpace, 0.0, 1.0); v_texcoord = a_textureCoords; + v_color = a_color; } `, @@ -111,12 +114,20 @@ export const Sdf: WebGlShaderType = { uniform int u_debug; varying vec2 v_texcoord; + varying vec4 v_color; float median(float r, float g, float b) { return max(min(r, g), min(max(r, g), b)); } void main() { + // Check if this is an underline/strikethrough quad (UV coordinates are very close to 0,0) + if (length(v_texcoord) < 0.001) { + // Render as solid color for underlines/strikethroughs, use uniform color + gl_FragColor = vec4(u_color.r, u_color.g, u_color.b, u_color.a); + return; + } + vec3 sample = texture2D(u_texture, v_texcoord).rgb; if (u_debug == 1) { gl_FragColor = vec4(sample.r, sample.g, sample.b, 1.0); @@ -124,11 +135,22 @@ export const Sdf: WebGlShaderType = { } float scaledDistRange = u_distanceRange * u_pixelRatio; float sigDist = scaledDistRange * (median(sample.r, sample.g, sample.b) - 0.5); - float opacity = clamp(sigDist + 0.5, 0.0, 1.0) * u_color.a; + float opacity = clamp(sigDist + 0.5, 0.0, 1.0); + + // Check if we should use uniform color or per-vertex color + vec4 finalColor; + if (v_color.r < 0.0) { + finalColor = u_color; + } else { + // Use per-vertex color from BBCode + finalColor = v_color; + } + + opacity *= finalColor.a; // Build the final color. // IMPORTANT: We must premultiply the color by the alpha value before returning it. - gl_FragColor = vec4(u_color.r * opacity, u_color.g * opacity, u_color.b * opacity, opacity); + gl_FragColor = vec4(finalColor.r * opacity, finalColor.g * opacity, finalColor.b * opacity, opacity); } `, }; diff --git a/src/core/text-rendering/CanvasFontHandler.ts b/src/core/text-rendering/CanvasFontHandler.ts index fbd23662..5bec47ea 100644 --- a/src/core/text-rendering/CanvasFontHandler.ts +++ b/src/core/text-rendering/CanvasFontHandler.ts @@ -39,7 +39,7 @@ const fontFamilies: Record = {}; const loadedFonts = new Set(); const fontLoadPromises = new Map>(); const normalizedMetrics = new Map(); -const nodesWaitingForFont: Record = Object.create(null); +const nodesWaitingForFont: Record> = {}; let initialized = false; let context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D; @@ -89,7 +89,8 @@ export const loadFont = async ( return existingPromise; } - const nwff: CoreTextNode[] = (nodesWaitingForFont[fontFamily] = []); + const nwff: Record = (nodesWaitingForFont[fontFamily] = + {}); // Create and store the loading promise const loadPromise = new FontFace(fontFamily, `url(${fontUrl})`) .load() @@ -102,7 +103,10 @@ export const loadFont = async ( setFontMetrics(fontFamily, normalizeMetrics(metrics)); } for (let key in nwff) { - nwff[key]!.setUpdateType(UpdateType.Local); + const node = nwff[key]; + if (node) { + node.setUpdateType(UpdateType.Local); + } } delete nodesWaitingForFont[fontFamily]; }) @@ -169,7 +173,10 @@ export const isFontLoaded = (fontFamily: string): boolean => { * @param node */ export const waitingForFont = (fontFamily: string, node: CoreTextNode) => { - nodesWaitingForFont[fontFamily]![node.id] = node; + if (!nodesWaitingForFont[fontFamily]) { + nodesWaitingForFont[fontFamily] = {}; + } + nodesWaitingForFont[fontFamily][node.id] = node; }; /** diff --git a/src/core/text-rendering/SdfTextRenderer.ts b/src/core/text-rendering/SdfTextRenderer.ts index 9839425c..0654c9eb 100644 --- a/src/core/text-rendering/SdfTextRenderer.ts +++ b/src/core/text-rendering/SdfTextRenderer.ts @@ -35,10 +35,10 @@ import type { WebGlCtxTexture } from '../renderers/webgl/WebGlCtxTexture.js'; import type { WebGlShaderNode } from '../renderers/webgl/WebGlShaderNode.js'; import { mergeColorAlpha } from '../../utils.js'; import type { TextLayout, GlyphLayout } from './TextRenderer.js'; -import { wrapText, measureLines } from './sdf/index.js'; +import { wrapText, measureLines, parseBBCode } from './sdf/index.js'; -// Each glyph requires 6 vertices (2 triangles) with 4 floats each (x, y, u, v) -const FLOATS_PER_VERTEX = 4; +// Each glyph requires 6 vertices (2 triangles) with 8 floats each (x, y, u, v, r, g, b, a) +const FLOATS_PER_VERTEX = 8; const VERTICES_PER_GLYPH = 6; // Type definition to match interface @@ -94,6 +94,135 @@ const renderText = (stage: Stage, props: CoreTextNodeProps): TextRenderInfo => { }; }; +/** + * Helper function to add a quad for underline/strikethrough with proper color + */ +const addSegmentQuadToBuffer = ( + vertexBuffer: Float32Array, + bufferIndex: number, + x1: number, + y1: number, + x2: number, + y2: number, + color?: number, +): number => { + let r: number, g: number, b: number, a: number; + + if (color !== undefined) { + // Use specific BBCode color for the segment + r = ((color >> 16) & 0xff) / 255.0; + g = ((color >> 8) & 0xff) / 255.0; + b = (color & 0xff) / 255.0; + a = 1.0; + } else { + // Use sentinel value (-1) to signal shader to use uniform color + r = -1.0; + g = -1.0; + b = -1.0; + a = -1.0; + } + + return addQuadToBuffer( + vertexBuffer, + bufferIndex, + x1, + y1, + x2, + y2, + 0.0, + 0.0, + 0.0, + 0.0, // No texture coordinates for solid color quads + r, + g, + b, + a, + ); +}; + +const addQuadToBuffer = ( + vertexBuffer: Float32Array, + bufferIndex: number, + x1: number, + y1: number, + x2: number, + y2: number, + u1: number, + v1: number, + u2: number, + v2: number, + r: number, + g: number, + b: number, + a: number, +): number => { + let index = bufferIndex; + + // Triangle 1: Top-left, top-right, bottom-left + // Vertex 1: Top-left + vertexBuffer[index++] = x1; + vertexBuffer[index++] = y1; + vertexBuffer[index++] = u1; + vertexBuffer[index++] = v1; + vertexBuffer[index++] = r; + vertexBuffer[index++] = g; + vertexBuffer[index++] = b; + vertexBuffer[index++] = a; + + // Vertex 2: Top-right + vertexBuffer[index++] = x2; + vertexBuffer[index++] = y1; + vertexBuffer[index++] = u2; + vertexBuffer[index++] = v1; + vertexBuffer[index++] = r; + vertexBuffer[index++] = g; + vertexBuffer[index++] = b; + vertexBuffer[index++] = a; + + // Vertex 3: Bottom-left + vertexBuffer[index++] = x1; + vertexBuffer[index++] = y2; + vertexBuffer[index++] = u1; + vertexBuffer[index++] = v2; + vertexBuffer[index++] = r; + vertexBuffer[index++] = g; + vertexBuffer[index++] = b; + vertexBuffer[index++] = a; + + // Triangle 2: Top-right, bottom-right, bottom-left + // Vertex 4: Top-right (duplicate) + vertexBuffer[index++] = x2; + vertexBuffer[index++] = y1; + vertexBuffer[index++] = u2; + vertexBuffer[index++] = v1; + vertexBuffer[index++] = r; + vertexBuffer[index++] = g; + vertexBuffer[index++] = b; + vertexBuffer[index++] = a; + + // Vertex 5: Bottom-right + vertexBuffer[index++] = x2; + vertexBuffer[index++] = y2; + vertexBuffer[index++] = u2; + vertexBuffer[index++] = v2; + vertexBuffer[index++] = r; + vertexBuffer[index++] = g; + vertexBuffer[index++] = b; + vertexBuffer[index++] = a; + + // Vertex 6: Bottom-left (duplicate) + vertexBuffer[index++] = x1; + vertexBuffer[index++] = y2; + vertexBuffer[index++] = u1; + vertexBuffer[index++] = v2; + vertexBuffer[index++] = r; + vertexBuffer[index++] = g; + vertexBuffer[index++] = b; + vertexBuffer[index++] = a; + + return index; +}; + /** * Add quads for rendering using cached layout data */ @@ -109,16 +238,35 @@ const addQuads = (layout?: TextLayout): Float32Array | null => { return null; } + // Count underline and strikethrough segments for additional quads + let decorationQuadCount = 0; + + // Count decoration segments by grouping consecutive decorated glyphs + for (let i = 0; i < glyphsLength; i++) { + const glyph = glyphs[i]; + const nextGlyph = glyphs[i + 1]; + + // If this is the end of an underline segment, count it + if (glyph?.underline === true && nextGlyph?.underline === false) { + decorationQuadCount++; + } + + // If this is the end of a strikethrough segment, count it + if (glyph?.strikethrough === true && nextGlyph?.strikethrough === false) { + decorationQuadCount++; + } + } + + const totalQuads = glyphsLength + decorationQuadCount; const vertexBuffer = new Float32Array( - glyphsLength * VERTICES_PER_GLYPH * FLOATS_PER_VERTEX, + totalQuads * VERTICES_PER_GLYPH * FLOATS_PER_VERTEX, ); let bufferIndex = 0; - let glyphIndex = 0; - while (glyphIndex < glyphsLength) { + // Add glyph quads first + for (let glyphIndex = 0; glyphIndex < glyphsLength; glyphIndex++) { const glyph = glyphs[glyphIndex]; - glyphIndex++; if (glyph === undefined) { continue; } @@ -133,43 +281,102 @@ const addQuads = (layout?: TextLayout): Float32Array | null => { const u2 = u1 + glyph.atlasWidth; const v2 = v1 + glyph.atlasHeight; - // Triangle 1: Top-left, top-right, bottom-left - // Vertex 1: Top-left - vertexBuffer[bufferIndex++] = x1; - vertexBuffer[bufferIndex++] = y1; - vertexBuffer[bufferIndex++] = u1; - vertexBuffer[bufferIndex++] = v1; - - // Vertex 2: Top-right - vertexBuffer[bufferIndex++] = x2; - vertexBuffer[bufferIndex++] = y1; - vertexBuffer[bufferIndex++] = u2; - vertexBuffer[bufferIndex++] = v1; - - // Vertex 3: Bottom-left - vertexBuffer[bufferIndex++] = x1; - vertexBuffer[bufferIndex++] = y2; - vertexBuffer[bufferIndex++] = u1; - vertexBuffer[bufferIndex++] = v2; - - // Triangle 2: Top-right, bottom-right, bottom-left - // Vertex 4: Top-right (duplicate) - vertexBuffer[bufferIndex++] = x2; - vertexBuffer[bufferIndex++] = y1; - vertexBuffer[bufferIndex++] = u2; - vertexBuffer[bufferIndex++] = v1; - - // Vertex 5: Bottom-right - vertexBuffer[bufferIndex++] = x2; - vertexBuffer[bufferIndex++] = y2; - vertexBuffer[bufferIndex++] = u2; - vertexBuffer[bufferIndex++] = v2; - - // Vertex 6: Bottom-left (duplicate) - vertexBuffer[bufferIndex++] = x1; - vertexBuffer[bufferIndex++] = y2; - vertexBuffer[bufferIndex++] = u1; - vertexBuffer[bufferIndex++] = v2; + // Extract color from glyph formatting or use uniform color + const glyphColor = glyph.color; + let r: number, g: number, b: number, a: number; + + if (glyphColor !== undefined) { + r = ((glyphColor >> 16) & 0xff) / 255.0; + g = ((glyphColor >> 8) & 0xff) / 255.0; + b = (glyphColor & 0xff) / 255.0; + a = 1.0; + } else { + r = -1.0; + g = -1.0; + b = -1.0; + a = -1.0; + } + + bufferIndex = addQuadToBuffer( + vertexBuffer, + bufferIndex, + x1, + y1, + x2, + y2, + u1, + v1, + u2, + v2, + r, + g, + b, + a, + ); + } + + // Add decoration segments + for (let glyphIndex = 0; glyphIndex < glyphsLength; glyphIndex++) { + const glyph = glyphs[glyphIndex]; + if (!glyph) continue; + + const nextGlyph = glyphs[glyphIndex + 1]; + + // Handle underline segments + if ( + glyph.underline === true && + (!nextGlyph || nextGlyph.underline === false) + ) { + let startIndex = glyphIndex; + while (startIndex > 0 && glyphs[startIndex - 1]?.underline === true) { + startIndex--; + } + + const startGlyph = glyphs[startIndex]; + const endGlyph = glyph; + if (!startGlyph) continue; + + const underlineY = glyph.y + glyph.height + 2; // Position slightly below text with small gap + const underlineThickness = Math.max(2, layout.lineHeight * 0.05); + + bufferIndex = addSegmentQuadToBuffer( + vertexBuffer, + bufferIndex, + startGlyph.x, + underlineY, + endGlyph.x + endGlyph.width, + underlineY + underlineThickness, + glyph.color, + ); + } + + // Handle strikethrough segments + if ( + glyph.strikethrough === true && + (!nextGlyph || nextGlyph.strikethrough === false) + ) { + let startIndex = glyphIndex; + while (startIndex > 0 && glyphs[startIndex - 1]?.strikethrough === true) { + startIndex--; + } + + const startGlyph = glyphs[startIndex]; + const endGlyph = glyph; + if (!startGlyph) continue; + + const strikethroughY = glyph.y + glyph.height * 0.45; // Position in middle of text + const strikethroughThickness = Math.max(2, layout.lineHeight * 0.04); + + bufferIndex = addSegmentQuadToBuffer( + vertexBuffer, + bufferIndex, + startGlyph.x, + strikethroughY, + endGlyph.x + endGlyph.width, + strikethroughY + strikethroughThickness, + glyph.color, + ); + } } return vertexBuffer; @@ -199,7 +406,7 @@ const renderQuads = ( // We can safely assume this is a WebGL renderer else this wouldn't be called const glw = (renderer as WebGlRenderer).glw; - const stride = 4 * Float32Array.BYTES_PER_ELEMENT; + const stride = 8 * Float32Array.BYTES_PER_ELEMENT; // 8 floats per vertex: x,y,u,v,r,g,b,a const webGlBuffer = glw.createBuffer(); if (!webGlBuffer) { @@ -227,6 +434,14 @@ const renderQuads = ( stride, offset: 2 * Float32Array.BYTES_PER_ELEMENT, }, + a_color: { + name: 'a_color', + size: 4, + type: glw.FLOAT as number, + normalized: false, + stride, + offset: 4 * Float32Array.BYTES_PER_ELEMENT, + }, }, }, ]); @@ -262,9 +477,36 @@ const renderQuads = ( 0, ); - // Add atlas texture and set quad count + let underlineSegmentCount = 0; + let inUnderlineSegment = false; + let strikethroughSegmentCount = 0; + let inStrikethroughSegment = false; + + for (let i = 0; i < layout.glyphs.length; i++) { + const glyph = layout.glyphs[i]; + if (glyph?.underline === true) { + if (!inUnderlineSegment) { + underlineSegmentCount++; + inUnderlineSegment = true; + } + } else { + inUnderlineSegment = false; + } + + if (glyph?.strikethrough === true) { + if (!inStrikethroughSegment) { + strikethroughSegmentCount++; + inStrikethroughSegment = true; + } + } else { + inStrikethroughSegment = false; + } + } + + const totalQuads = + layout.glyphs.length + underlineSegmentCount + strikethroughSegmentCount; renderOp.addTexture(atlasTexture.ctxTexture as WebGlCtxTexture); - renderOp.numQuads = layout.glyphs.length; + renderOp.numQuads = totalQuads; (renderer as WebGlRenderer).addRenderOp(renderOp); }; @@ -321,10 +563,14 @@ const generateTextLayout = ( const hasMaxLines = effectiveMaxLines > 0; + // Parse BBCode and create a character map with formatting information + const parsedText = parseBBCode(text); + const plainText = parsedText.text; + // Split text into lines based on wrapping constraints const [lines, remainingLines, remainingText] = shouldWrapText ? wrapText( - text, + plainText, fontFamily, finalScale, maxWidth, @@ -335,7 +581,7 @@ const generateTextLayout = ( hasMaxLines, ) : measureLines( - text.split('\n'), + plainText.split('\n'), fontFamily, letterSpacing, finalScale, @@ -348,14 +594,16 @@ const generateTextLayout = ( let currentY = 0; for (let i = 0; i < lines.length; i++) { - if (lines[i]![1] > maxWidthFound) { - maxWidthFound = lines[i]![1]; + const line = lines[i]; + if (line && line[1] > maxWidthFound) { + maxWidthFound = line[1]; } } // Second pass: Generate glyph layouts with proper alignment let lineIndex = 0; const linesLength = lines.length; + let globalCharIndex = 0; // Track position in original plain text for formatting lookup while (lineIndex < linesLength) { const [line, lineWidth] = lines[lineIndex]!; @@ -389,14 +637,24 @@ const generateTextLayout = ( continue; } + // Sync globalCharIndex with plainText by finding the next matching character + while ( + globalCharIndex < plainText.length && + plainText[globalCharIndex] !== char + ) { + globalCharIndex++; + } + // Skip zero-width spaces for rendering but keep them in the text flow if (isZeroWidthSpace(char)) { + globalCharIndex++; continue; } // Get glyph data from font handler const glyph = SdfFontHandler.getGlyph(fontFamily, codepoint); if (glyph === null) { + globalCharIndex++; continue; } @@ -413,6 +671,9 @@ const generateTextLayout = ( advance += kerning; } + // Get formatting for this character position + const formatting = parsedText.formatting[globalCharIndex]; + // Calculate glyph position and atlas coordinates (in design units) const glyphLayout: GlyphLayout = { codepoint, @@ -427,6 +688,9 @@ const generateTextLayout = ( atlasY: glyph.y / atlasHeight, atlasWidth: glyph.width / atlasWidth, atlasHeight: glyph.height / atlasHeight, + underline: formatting?.underline || false, + strikethrough: formatting?.strikethrough || false, + color: formatting?.color, }; glyphs.push(glyphLayout); @@ -434,6 +698,7 @@ const generateTextLayout = ( // Advance position with letter spacing (in design units) currentX += advance + designLetterSpacing; prevCodepoint = codepoint; + globalCharIndex++; } currentY += designLineHeight; diff --git a/src/core/text-rendering/TextRenderer.ts b/src/core/text-rendering/TextRenderer.ts index 67ad978f..5c06ca28 100644 --- a/src/core/text-rendering/TextRenderer.ts +++ b/src/core/text-rendering/TextRenderer.ts @@ -289,6 +289,18 @@ export interface GlyphLayout { atlasY: number; atlasWidth: number; atlasHeight: number; + /** + * Whether this glyph should be underlined + */ + underline?: boolean; + /** + * Whether this glyph should be struck through + */ + strikethrough?: boolean; + /** + * Color override for this glyph (0xRRGGBB format) + */ + color?: number; } /** diff --git a/src/core/text-rendering/sdf/Utils.ts b/src/core/text-rendering/sdf/Utils.ts index 86d48ec9..1ba6cf18 100644 --- a/src/core/text-rendering/sdf/Utils.ts +++ b/src/core/text-rendering/sdf/Utils.ts @@ -21,6 +21,187 @@ import { isZeroWidthSpace } from '../Utils.js'; import * as SdfFontHandler from '../SdfFontHandler.js'; import type { TextLineStruct, WrappedLinesStruct } from '../TextRenderer.js'; +/** + * BBCode formatting information for a character + */ +export interface CharacterFormatting { + underline?: boolean; + bold?: boolean; + italic?: boolean; + strikethrough?: boolean; + color?: number; // Color in 0xRRGGBB format +} + +/** + * Result of BBCode parsing + */ +export interface ParsedBBCode { + text: string; + formatting: Record; +} + +/** + * Parse color value from BBCode color attribute + * Supports hex colors (#ff0000, #f00) and named colors + */ +export const parseColor = (colorValue: string): number | null => { + const trimmed = colorValue.trim(); + + // Handle hex colors + if (trimmed.startsWith('#')) { + const hex = trimmed.substring(1); + + // Handle 3-digit hex (#f00 -> #ff0000) + if (hex.length === 3) { + const r = parseInt(hex.charAt(0) + hex.charAt(0), 16); + const g = parseInt(hex.charAt(1) + hex.charAt(1), 16); + const b = parseInt(hex.charAt(2) + hex.charAt(2), 16); + if (!isNaN(r) && !isNaN(g) && !isNaN(b)) { + return (r << 16) | (g << 8) | b; + } + } + + // Handle 6-digit hex (#ff0000) + if (hex.length === 6) { + const color = parseInt(hex, 16); + if (!isNaN(color)) { + return color; + } + } + } + + // Handle named colors + const namedColors: Record = { + red: 0xff0000, + green: 0x00ff00, + blue: 0x0000ff, + white: 0xffffff, + black: 0x000000, + yellow: 0xffff00, + cyan: 0x00ffff, + magenta: 0xff00ff, + orange: 0xff8000, + purple: 0x800080, + pink: 0xff69b4, + brown: 0xa52a2a, + gray: 0x808080, + grey: 0x808080, + }; + + const lowerName = trimmed.toLowerCase(); + if (namedColors[lowerName] !== undefined) { + return namedColors[lowerName]; + } + + return null; +}; + +/** + * BBCode parser that supports [u] for underline, [b] for bold, [i] for italic, [s] for strikethrough, and [color=value] for colors + * Returns plain text and character-level formatting information + */ +export const parseBBCode = (text: string): ParsedBBCode => { + const result: ParsedBBCode = { + text: '', + formatting: {}, + }; + + let currentFormatting: CharacterFormatting = {}; + let i = 0; + let outputIndex = 0; + + while (i < text.length) { + // Check for BBCode tags + if (text[i] === '[') { + let tagEnd = text.indexOf(']', i); + + if (tagEnd !== -1) { + const tag = text.substring(i + 1, tagEnd).toLowerCase(); + let isClosingTag = false; + let tagName = tag; + + if (tag.startsWith('/')) { + isClosingTag = true; + tagName = tag.substring(1); + } + + // Handle supported tags + if (tagName === 'u') { + if (isClosingTag) { + currentFormatting = { ...currentFormatting }; + delete currentFormatting.underline; + } else { + currentFormatting = { ...currentFormatting, underline: true }; + } + i = tagEnd + 1; + continue; + } + // Add support for other tags if needed + else if (tagName === 'b') { + if (isClosingTag) { + currentFormatting = { ...currentFormatting }; + delete currentFormatting.bold; + } else { + currentFormatting = { ...currentFormatting, bold: true }; + } + i = tagEnd + 1; + continue; + } else if (tagName === 'i') { + if (isClosingTag) { + currentFormatting = { ...currentFormatting }; + delete currentFormatting.italic; + } else { + currentFormatting = { ...currentFormatting, italic: true }; + } + i = tagEnd + 1; + continue; + } else if (tagName === 's') { + if (isClosingTag) { + currentFormatting = { ...currentFormatting }; + delete currentFormatting.strikethrough; + } else { + currentFormatting = { ...currentFormatting, strikethrough: true }; + } + i = tagEnd + 1; + continue; + } else if (tagName.startsWith('color=')) { + if (!isClosingTag) { + // Parse color value from tag like [color=#ff0000] or [color=red] + const colorValue = tagName.substring(6); // Remove 'color=' + const parsedColor = parseColor(colorValue); + if (parsedColor !== null) { + currentFormatting = { ...currentFormatting, color: parsedColor }; + } + } else { + currentFormatting = { ...currentFormatting }; + delete currentFormatting.color; + } + i = tagEnd + 1; + continue; + } else if (tagName === 'color') { + // Handle closing [/color] tag + if (isClosingTag) { + currentFormatting = { ...currentFormatting }; + delete currentFormatting.color; + i = tagEnd + 1; + continue; + } + } + } + } + + // Regular character - add to output with current formatting + result.text += text[i]; + if (Object.keys(currentFormatting).length > 0) { + result.formatting[outputIndex] = { ...currentFormatting }; + } + outputIndex++; + i++; + } + + return result; +}; + export const measureLines = ( lines: string[], fontFamily: string, diff --git a/visual-regression/certified-snapshots/chromium-ci/text-bbcode-1.png b/visual-regression/certified-snapshots/chromium-ci/text-bbcode-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b491ec33806e530751516e2d9c23d7bf97e6c7e4 GIT binary patch literal 64379 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
y{1%4X8NO`ImS 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