Skip to content

Commit

Permalink
Merge pull request #15 from ray-pH/multiline-text
Browse files Browse the repository at this point in the history
Multiline text
  • Loading branch information
ray-pH authored Feb 8, 2024
2 parents 4d97800 + aab84cc commit 8f170d1
Show file tree
Hide file tree
Showing 4 changed files with 359 additions and 11 deletions.
152 changes: 152 additions & 0 deletions src/BBcode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// BBCode parser for multiline text object
//

enum BB_TokenType {
TEXT = "TEXT",
OPEN_TAG = "OPEN_TAG",
CLOSE_TAG = "CLOSE_TAG",
EOF = "EOF",
}
type BB_Token = {
type : BB_TokenType,
attributes : { [key: string]: string }
}

export class BB_Lexer {
static parse_tag_content(str : string) : BB_Token {
if (str[0] === "/") {
// close tag
let name = str.substring(1);
return {
type: BB_TokenType.CLOSE_TAG,
attributes: {_tag_name : name}
}
}

// open tag
let space_id = str.indexOf(" ");
let equal_id = str.indexOf("=");
if (space_id === -1 && equal_id === -1) {
// [name]
return {
type: BB_TokenType.OPEN_TAG,
attributes: {_tag_name : str}
}
}
if (space_id === -1 && equal_id > 0) {
// [name=value]
let name = str.substring(0, equal_id);
let value = str.substring(equal_id + 1);
let attributes : any = {_tag_name : name}
attributes[name] = value;
return {
type: BB_TokenType.OPEN_TAG,
attributes
}
}
// [name attr1=value1 attr2=value2]
throw new Error("Unimplemented");
}

static parse(text : string) : BB_Token[] | null {
let tokens : BB_Token[] = [];

let pos = 0;
let len = text.length;
while (pos < len) {
// Find the next tag
// Find [
let TagLeft = text.indexOf("[", pos);
if (TagLeft === -1) {
// no more tags, add the rest of the text
tokens.push({
type: BB_TokenType.TEXT,
attributes: {_text : text.substring(pos)}
});
break;
}
if (TagLeft > pos) {
// add the text before the [
tokens.push({
type: BB_TokenType.TEXT,
attributes: {_text : text.substring(pos, TagLeft)}
});
}

// find ]
let TagRight = text.indexOf("]", TagLeft);
let nextTagLeft = text.indexOf("[", TagLeft + 1);
// make sure there is no [ between the [ and ]
if (nextTagLeft > 0 && nextTagLeft < TagRight) return null;
// make sure there is a ] after the [
if (TagRight === -1) return null;

let tag_content = text.substring(TagLeft + 1, TagRight);
tokens.push(BB_Lexer.parse_tag_content(tag_content));

pos = TagRight + 1;
}
return tokens;
}
}

export class BB_multiline {
static from_BBCode(text : string, linespace : string = "1em") {
let tspans : {text : string, style : {}}[] = []
let tag_stack : { [key: string]: string }[] = [];
let tokens = BB_Lexer.parse(text);
if (tokens === null) {
console.error("Invalid BBCode");
return;
}
for (let token of tokens) {
switch (token.type) {
case BB_TokenType.OPEN_TAG: {
// if the token is [br] then add a new line
if (token.attributes['_tag_name'] === "br") {
tspans.push({text: "\n", style: {dy: linespace}});
break;
}
tag_stack.push(token.attributes);
} break;
case BB_TokenType.CLOSE_TAG: {
if (tag_stack.length === 0) {
console.error("Invalid BBCode");
return;
}
let tag_top = tag_stack[tag_stack.length - 1];
if (tag_top['_tag_name'] !== token.attributes['_tag_name']) {
console.error("Invalid BBCode");
return;
}
tag_stack.pop();
} break;
case BB_TokenType.TEXT: {
let style = BB_multiline.build_style(tag_stack);
tspans.push({text: token.attributes['_text'], style});
} break;
}
}

return tspans;
}

static build_style(tag_stack : { [key: string]: string }[]) {
let style : any = {};
for (let tag of tag_stack) {
switch (tag['_tag_name']) {
case "b" : style["font-weight"] = "bold"; break;
case "i" : style["font-style"] = "italic"; break;
case "color" : style["fill"] = tag["color"]; break;
case "size" : style["font-size"] = tag["size"]; break;
case "dx" : style["dx"] = tag["dx"]; break;
case "dy" : style["dy"] = tag["dy"]; break;
case "font" : style["font-family"] = tag["font"]; break;
case "var" : style["textvar"] = true; break;
case "tag" : style["tag"] = tag["tag"]; break;
}
}
return style;
}
}

56 changes: 51 additions & 5 deletions src/diagram.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Vector2, V2, Transform } from './vector.js';
import { BB_multiline } from './BBcode.js'

function assert(condition : boolean, message : string) : void {
if (!condition) {
Expand All @@ -12,6 +13,7 @@ export enum DiagramType {
Text = 'text',
Image = 'image',
Diagram = 'diagram',
MultilineText = 'multilinetext',
}

export type Anchor =
Expand Down Expand Up @@ -56,6 +58,20 @@ export type ImageData = {
"src" : string,
}

type ExtraTspanStyle = {
"dy" : string,
"dx" : string,
"textvar" : boolean,
"tag" : string,
}
type TextSpanData = {
"text" : string,
"style" : Partial<TextData> & Partial<DiagramStyle> & Partial<ExtraTspanStyle>,
}
export type MultilineTextData = {
"content" : TextSpanData[],
}

function anchor_to_textdata(anchor : Anchor) : Partial<TextData> {
// TODO : might want to look at
// hanging vs text-before-edge
Expand Down Expand Up @@ -89,10 +105,11 @@ export class Diagram {
children : Diagram[] = [];
path : Path | undefined = undefined; // Polygon and Curve have a path
origin : Vector2 = new Vector2(0, 0); // position of the origin of the diagram
style : Partial<DiagramStyle> = {};
textdata : Partial<TextData> = {};
imgdata : Partial<ImageData> = {};
mutable : boolean = false;
style : Partial<DiagramStyle> = {};
textdata : Partial<TextData> = {};
multilinedata : Partial<MultilineTextData> = {};
imgdata : Partial<ImageData> = {};
mutable : boolean = false;
tags : string[] = [];

constructor(type_ : DiagramType,
Expand All @@ -101,13 +118,15 @@ export class Diagram {
children? : Diagram[],
textdata? : Partial<TextData>,
imgdata? : Partial<ImageData>
multilinedata? : Partial<MultilineTextData>
} = {}
) {
this.type = type_;
this.path = args.path;
if (args.children) { this.children = args.children; }
if (args.textdata) { this.textdata = args.textdata; }
if (args.imgdata) { this.imgdata = args.imgdata; }
if (args.multilinedata) { this.multilinedata = args.multilinedata; }
}

/**
Expand Down Expand Up @@ -351,7 +370,7 @@ export class Diagram {

private update_textdata(textdataname : keyof Diagram['textdata'], textdatavalue : string) : Diagram {
let newd : Diagram = this.copy_if_not_mutable();
if (newd.type == DiagramType.Text) {
if (newd.type == DiagramType.Text || newd.type == DiagramType.MultilineText) {
newd.textdata[textdataname] = textdatavalue;
} else if (newd.type == DiagramType.Diagram) {
// newd.children = newd.children.map(c => c.update_textdata(textdataname, textdatavalue));
Expand Down Expand Up @@ -1007,6 +1026,33 @@ export function image(src : string, width : number, height: number){
return img;
}

/**
* Create a multiline text diagram
* @param strs list of text to display
*/
export function multiline(spans : ([string] | [string,Partial<TextData>])[]) : Diagram {
let tspans : TextSpanData[] = [];
for (let i = 0; i < spans.length; i++) {
let text = spans[i][0];
let style = spans[i][1] ?? {};
tspans.push({text, style});
}
let dmulti = new Diagram(DiagramType.MultilineText, {
multilinedata : { content : tspans },
path : new Path([new Vector2(0, 0)]),
});
return dmulti;
}

export function multiline_bb(bbstr : string, linespace? : string) : Diagram {
let tspans : TextSpanData[] = BB_multiline.from_BBCode(bbstr,linespace) as TextSpanData[];
let dmulti = new Diagram(DiagramType.MultilineText, {
multilinedata : { content : tspans },
path : new Path([new Vector2(0, 0)]),
});
return dmulti;
}


// END primitives =============================

Expand Down
Loading

0 comments on commit 8f170d1

Please sign in to comment.