Skip to content
This repository has been archived by the owner on Jun 28, 2021. It is now read-only.

Commit

Permalink
feat: Add support for BBCode code style (#78)
Browse files Browse the repository at this point in the history
* Add support for alternative tag styles, still default to <xml></xml> style. Makes it possible to do [bb][/bb] code style

* add out of the box bbcode tags that can take parameters - optional

also retains old xml style functionality without any regressions

* fix demo is not loading

* clip partially showing tags at the end of clipped text - useful for scrolling

* make absolutely sure no incomplete tags end up on the screen

* fix color tag with hashtag not getting picked up

* use enums for tag styles

* revert any changes to demo, due to prettier messing up its formatting

* revert changes to demo

* formatting

* fix pixi-multistyle crashing when close tag is in wrong place, cleanup incomplete tags for bot styles

* format

* update demo to show bbcode tags use

* remove redundant hack to get demo to run on linux
  • Loading branch information
blurymind authored and tleunen committed Nov 12, 2019
1 parent 1a402cd commit 2a7be20
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 16 deletions.
40 changes: 32 additions & 8 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ <h2>Basics</h2>
}
});</code></pre>

<h2>Tag Styles</h2>
<pre><code class="js">let text = new MultiStyleText(&quot;Let's make some [b]bold[/b]\n [color=yellow][i]multistyle[/i][/color] [size=40]bbcode[/size] [outline=purple]tags[/outline] text for \n[spacing=10]Pixi.js![/spacing]&quot;,
{
&quot;default&quot;: {
tagStyle: &quot;bbcode&quot;, //bbcode and xml (default) supported..
fill: &quot;#cccccc&quot;,
align: &quot;center&quot;
},
});</code></pre>

<h2>Nesting Tags</h2>
<pre><code class="js">let nested = new MultiStyleText(&quot;You can &lt;outline&gt;nest &lt;b&gt;tags &lt;red&gt;as &lt;i&gt;deeply &lt;thicker&gt;as &lt;shadow&gt;you'd &lt;large&gt;like&lt;/large&gt;&lt;/shadow&gt;&lt;/thicker&gt;&lt;/i&gt;&lt;/red&gt;&lt;/b&gt;&lt;/outline&gt;&quot;,
{
Expand Down Expand Up @@ -189,7 +199,7 @@ <h2>Have Fun</h2>
<div id="pixi-container">
<script>
PIXI.settings.RESOLUTION = 2;
let renderer = PIXI.autoDetectRenderer(600, 3100);
let renderer = PIXI.autoDetectRenderer(600, 3425);
renderer.backgroundColor = 0x333333;
document.getElementById("pixi-container").appendChild(renderer.view);
let stage = new PIXI.Container();
Expand Down Expand Up @@ -221,6 +231,20 @@ <h2>Have Fun</h2>
text.y = 150;
stage.addChild(text);

// Tag Styles
let bbcode = new MultiStyleText("Let's make some [b]bold[/b]\n [color=yellow][i]multistyle[/i][/color] [size=40]bbcode[/size] [outline=purple]tags[/outline] text for \n[spacing=10]Pixi.js![/spacing]",
{
"default": {
tagStyle: "bbcode",
fill: "#cccccc",
align: "center"
}
});

bbcode.x = 300 - bbcode.width / 2;
bbcode.y = 550;
stage.addChild(bbcode);

// Nesting Tags
let nested = new MultiStyleText("You can <outline>nest <b>tags <red>as <i>deeply <thicker>as <shadow>you'd <large>like</large></shadow></thicker></i></red></b></outline>",
{
Expand All @@ -239,7 +263,7 @@ <h2>Have Fun</h2>
});

nested.x = 300 - nested.width / 2;
nested.y = 550;
nested.y = 960;
stage.addChild(nested);

// Vertical Alignment
Expand All @@ -265,7 +289,7 @@ <h2>Have Fun</h2>
});

valign.x = 300 - valign.width / 2;
valign.y = 960;
valign.y = 1280;
stage.addChild(valign);

// Wrapping and Alignment
Expand All @@ -282,7 +306,7 @@ <h2>Have Fun</h2>
});

wrapping.x = 550 - wrapping.width;
wrapping.y = 1220;
wrapping.y = 1600;
stage.addChild(wrapping);

// Wrapping and Alignment II
Expand All @@ -302,7 +326,7 @@ <h2>Have Fun</h2>
});

wrapping2.x = 440 - wrapping2.width;
wrapping2.y = 1700;
wrapping2.y = 2100;
stage.addChild(wrapping2);

// Debug Mode
Expand All @@ -329,7 +353,7 @@ <h2>Have Fun</h2>
MultiStyleText.debugOptions.objects.enabled = true;

debug.x = 300 - debug.width / 2;
debug.y = 2050;
debug.y = 2400;
stage.addChild(debug);

MultiStyleText.debugOptions.spans.enabled = false;
Expand All @@ -352,7 +376,7 @@ <h2>Have Fun</h2>
});

interaction.x = 300 - interaction.width / 2;
interaction.y = 2480;
interaction.y = 2800;
stage.addChild(interaction);

interaction.interactive = true;
Expand Down Expand Up @@ -382,7 +406,7 @@ <h2>Have Fun</h2>
let fun = new MultiStyleText("Now have fun making some <blue>beautiful</blue> <red>multistyle</red> text!", funStyles);

fun.x = 300 - fun.width / 2;
fun.y = 2900;
fun.y = 3250;
stage.addChild(fun);

// Animate
Expand Down
66 changes: 58 additions & 8 deletions src/pixi-multistyle-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
export interface ExtendedTextStyle extends PIXI.TextStyleOptions {
valign?: "top" | "middle" | "bottom" | "baseline" | number;
debug?: boolean;
tagStyle?: "xml" | "bbcode";
}

export interface TextStyleSet {
Expand Down Expand Up @@ -91,6 +92,16 @@ const INTERACTION_EVENTS = [
"touchleave"
];

const TAG_STYLE = {
bbcode: "bbcode",
xml: "xml"
};

const TAG = {
bbcode: ["[", "]"],
xml: ["<", ">"]
};

export default class MultiStyleText extends PIXI.Text {
private static DEFAULT_TAG_STYLE: ExtendedTextStyle = {
align: "left",
Expand Down Expand Up @@ -118,7 +129,8 @@ export default class MultiStyleText extends PIXI.Text {
textBaseline: "alphabetic",
valign: "baseline",
wordWrap: false,
wordWrapWidth: 100
wordWrapWidth: 100,
tagStyle: "xml"
};

public static debugOptions: MstDebugOptions = {
Expand Down Expand Up @@ -171,6 +183,19 @@ export default class MultiStyleText extends PIXI.Text {
this.textStyles[style] = this.assign({}, styles[style]);
}
}
if (this.textStyles.default.tagStyle === TAG_STYLE.bbcode) {
// when using bbcode parsing, register a bunch of standard bbcode tags and some cool pixi ones
this.textStyles.b = this.assign({}, {fontStyle: 'bold'});
this.textStyles.i = this.assign({}, {fontStyle: 'italic'});
this.textStyles.color = this.assign({}, {fill: ''}); // an array would result in gradients
this.textStyles.outline = this.assign({}, {stroke: '', strokeThickness: 6});
this.textStyles.font = this.assign({}, {fontFamily: ''});
this.textStyles.shadow = this.assign({}, {
dropShadowColor: '', dropShadow: true, dropShadowBlur: 3, dropShadowDistance: 3, dropShadowAngle: 2,});
this.textStyles.size = this.assign({}, {fontSize: 'px'});
this.textStyles.spacing = this.assign({}, {letterSpacing: ''});
this.textStyles.align = this.assign({}, {align: ''});
}

this._style = new PIXI.TextStyle(this.textStyles["default"]);
this.dirty = true;
Expand Down Expand Up @@ -200,14 +225,16 @@ export default class MultiStyleText extends PIXI.Text {

private getTagRegex(captureName: boolean, captureMatch: boolean): RegExp {
let tagAlternation = Object.keys(this.textStyles).join("|");
const { tagStyle } = this.textStyles.default;

if (captureName) {
tagAlternation = `(${tagAlternation})`;
} else {
tagAlternation = `(?:${tagAlternation})`;
}

let reStr = `<${tagAlternation}(?:\\s+[A-Za-z0-9_\\-]+=(?:"(?:[^"]+|\\\\")*"|'(?:[^']+|\\\\')*'))*\\s*>|</${tagAlternation}\\s*>`;
let reStr = tagStyle === TAG_STYLE.bbcode ? `\\${TAG.bbcode[0]}${tagAlternation}(?:\\=(?:[A-Za-z0-9_\\-\\#]+|'(?:[^']+|\\\\')*'))*\\s*\\${TAG.bbcode[1]}|\\${TAG.bbcode[0]}\\/${tagAlternation}\\s*\\${TAG.bbcode[1]}`
: `\\${TAG.xml[0]}${tagAlternation}(?:\\s+[A-Za-z0-9_\\-]+=(?:"(?:[^"]+|\\\\")*"|'(?:[^']+|\\\\')*'))*\\s*\\${TAG.xml[1]}|\\${TAG.xml[0]}\\/${tagAlternation}\\s*\\${TAG.xml[1]}`;

if (captureMatch) {
reStr = `(${reStr})`;
Expand All @@ -220,6 +247,10 @@ export default class MultiStyleText extends PIXI.Text {
return new RegExp(`([A-Za-z0-9_\\-]+)=(?:"((?:[^"]+|\\\\")*)"|'((?:[^']+|\\\\')*)')`, "g");
}

private getBBcodePropertyRegex(): RegExp {
return new RegExp(`[A-Za-z0-9_\\-]+=([A-Za-z0-9_\\-\\#]+)`, "g");
}

private _getTextDataPerLine (lines: string[]) {
let outputTextData: TextData[][] = [];
let re = this.getTagRegex(true, false);
Expand All @@ -238,7 +269,6 @@ export default class MultiStyleText extends PIXI.Text {
while (matchArray = re.exec(lines[i])) {
matches.push(matchArray);
}

// if there is no match, we still need to add the line with the default style
if (matches.length === 0) {
lineTextData.push(this.createTextData(lines[i], styleStack[styleStack.length - 1], tagStack[tagStack.length - 1]));
Expand All @@ -263,8 +293,6 @@ export default class MultiStyleText extends PIXI.Text {
tagStack.pop();
}
} else { // set the current style
styleStack.push(this.assign({}, styleStack[styleStack.length - 1], this.textStyles[matches[j][1]]));

let properties: { [key: string]: string } = {};
let propertyRegex = this.getPropertyRegex();
let propertyMatch: RegExpMatchArray;
Expand All @@ -274,6 +302,21 @@ export default class MultiStyleText extends PIXI.Text {
}

tagStack.push({ name: matches[j][1], properties });

const { tagStyle } = this.textStyles.default;
// if using bbtag style, take styling information in a different way
if (tagStyle === TAG_STYLE.bbcode && matches[j][0].includes('=') && this.textStyles[matches[j][1]]) {
const bbcodeRegex = this.getBBcodePropertyRegex();
const bbcodeTags = bbcodeRegex.exec(matches[j][0]);
let bbStyle:{ [key: string]: string } = {};
Object.entries(this.textStyles[matches[j][1]]).forEach( style => {
bbStyle[style[0]] = typeof style[1] !== 'string'? style[1] : bbcodeTags[1] + style[1];
})
styleStack.push(this.assign({}, styleStack[styleStack.length - 1], bbStyle));

} else {
styleStack.push(this.assign({}, styleStack[styleStack.length - 1], this.textStyles[matches[j][1]]));
}
}

// update the current search index
Expand All @@ -282,17 +325,24 @@ export default class MultiStyleText extends PIXI.Text {

// is there any character left?
if (currentSearchIdx < lines[i].length) {
lineTextData.push(this.createTextData(
lines[i].substring(currentSearchIdx),
const result = this.createTextData(
currentSearchIdx ? lines[i].substring(currentSearchIdx) : lines[i],
styleStack[styleStack.length - 1],
tagStack[tagStack.length - 1]
));
)
lineTextData.push(result);
}
}

outputTextData.push(lineTextData);
}

// don't display any incomplete tags at the end of text- good for scrolling text in games
const { tagStyle } = this.textStyles.default;
outputTextData[outputTextData.length-1].map( data => {
if (data.text.includes(TAG[tagStyle][0])) data.text = data.text.match(tagStyle === TAG_STYLE.bbcode ? /^(.*)\[/ : /^(.*)\</)[1]
});

return outputTextData;
}

Expand Down

0 comments on commit 2a7be20

Please sign in to comment.