@m2c2kit/core 0.3.28 → 0.3.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,21 +1,13 @@
1
- MIT License
1
+ Copyright 2023 Scott T. Yabiku
2
2
 
3
- Copyright (c) 2023 Scott T. Yabiku
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
4
6
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
7
+ http://www.apache.org/licenses/LICENSE-2.0
11
8
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @m2c2kit/core
2
2
 
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
3
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/license/apache-2-0)
4
4
  [![CI/CD](https://github.com/m2c2-project/m2c2kit/actions/workflows/ci.yml/badge.svg)](https://github.com/m2c2-project/m2c2kit/actions/workflows/ci.yml)
5
5
  [![npm version](https://img.shields.io/npm/v/@m2c2kit/core.svg)](https://www.npmjs.com/package/@m2c2kit/core)
6
6
 
package/dist/index.d.ts CHANGED
@@ -1846,7 +1846,9 @@ declare class Game implements Activity {
1846
1846
  /**
1847
1847
  * Adds a scene to the game.
1848
1848
  *
1849
- * @remarks A scene, and its children nodes, cannot be presented unless it has been added to the game object.
1849
+ * @remarks A scene, and its children nodes, cannot be presented unless it
1850
+ * has been added to the game object. A scene can be added to the game
1851
+ * only once.
1850
1852
  *
1851
1853
  * @param scene
1852
1854
  */
@@ -4195,6 +4197,8 @@ declare enum LabelHorizontalAlignmentMode {
4195
4197
  }
4196
4198
 
4197
4199
  interface LabelOptions extends M2NodeOptions, DrawableOptions, TextOptions {
4200
+ /** Text to be displayed. Tags for bold, italic, and underline are supported, e.g., `<b><u>Bold and underline</u></b>`. */
4201
+ text?: string;
4198
4202
  /** Horizontal alignment of label text. see {@link LabelHorizontalAlignmentMode}. Default is LabelHorizontalAlignmentMode.center */
4199
4203
  horizontalAlignmentMode?: LabelHorizontalAlignmentMode;
4200
4204
  /** Maximum width of label text before wrapping occurs. Default is the canvas width */
@@ -4226,13 +4230,19 @@ declare class Label extends M2Node implements IDrawable, IText, LabelOptions {
4226
4230
  private builder?;
4227
4231
  private _fontPaint?;
4228
4232
  private _backgroundPaint?;
4233
+ private _underlinePaint?;
4229
4234
  private localizedFontSize;
4230
4235
  private localizedFontName;
4231
4236
  private localizedFontNames;
4237
+ private plainText;
4238
+ private styleSegments;
4239
+ private underlinedRanges;
4240
+ private currentBuilderPosition;
4232
4241
  /**
4233
4242
  * Single or multi-line text formatted and rendered on the screen.
4234
4243
  *
4235
- * @remarks Label (in contrast to TextLine) has enhanced text support for line wrapping, centering/alignment, and background colors.
4244
+ * @remarks Label (in contrast to TextLine) has enhanced text support for
4245
+ * line wrapping, centering/alignment, and background colors.
4236
4246
  *
4237
4247
  * @param options - {@link LabelOptions}
4238
4248
  */
@@ -4263,6 +4273,19 @@ declare class Label extends M2Node implements IDrawable, IText, LabelOptions {
4263
4273
  suppressEvents?: boolean;
4264
4274
  };
4265
4275
  initialize(): void;
4276
+ /**
4277
+ * Parses text with formatting tags and returns plain text and style segments.
4278
+ * Supports <b> for bold, <i> for italics, and <u> for underline.
4279
+ * Properly handles nested tags like <b><u>bold and underlined</u></b>.
4280
+ * Throws errors for malformed tags, but treats unknown tags as plain text.
4281
+ *
4282
+ * @param text - The text with formatting tags
4283
+ * @returns The parsed text result
4284
+ * @throws Error if tags are improperly nested or unclosed
4285
+ */
4286
+ private parseFormattedText;
4287
+ private addTextWithStylesToParagraphBuilder;
4288
+ private addTextWithStyle;
4266
4289
  /**
4267
4290
  * Determines the M2Font objects that need to be ready in order to draw
4268
4291
  * the Label.
@@ -4303,6 +4326,8 @@ declare class Label extends M2Node implements IDrawable, IText, LabelOptions {
4303
4326
  private set backgroundPaint(value);
4304
4327
  private get fontPaint();
4305
4328
  private set fontPaint(value);
4329
+ private get underlinePaint();
4330
+ private set underlinePaint(value);
4306
4331
  /**
4307
4332
  * Duplicates a node using deep copy.
4308
4333
  *
@@ -4316,6 +4341,7 @@ declare class Label extends M2Node implements IDrawable, IText, LabelOptions {
4316
4341
  duplicate(newName?: string): Label;
4317
4342
  update(): void;
4318
4343
  draw(canvas: Canvas): void;
4344
+ private drawUnderlines;
4319
4345
  warmup(canvas: Canvas): void;
4320
4346
  }
4321
4347
 
@@ -5341,4 +5367,5 @@ declare class WebGlInfo {
5341
5367
  static dispose(): void;
5342
5368
  }
5343
5369
 
5344
- export { Action, type Activity, type ActivityCallbacks, type ActivityEvent, type ActivityEventListener, type ActivityKeyValueData, type ActivityLifecycleEvent, type ActivityResultsEvent, ActivityType, type BrowserImage, type BrowserImageDataReadyEvent, type CallbackOptions, CanvasKitHelpers, ColorfulMutablePath, Composite, type CompositeEvent, type CompositeOptions, Constants, ConstraintType, type Constraints, CustomAction, type CustomActionOptions, type DefaultParameter, Dimensions, type DomPointerDownEvent, type DrawableOptions, type EasingFunction, Easings, Equal, Equals, EventStore, EventStoreMode, FadeAlphaAction, type FadeAlphaActionOptions, type FontAsset, type FontData, FontManager, Game, type GameData, type GameEvent, type GameOptions, type GameParameters, type GlobalVariables, GroupAction, I18n, type I18nDataReadyEvent, type IDataStore, type IDrawable, type IText, ImageManager, Label, LabelHorizontalAlignmentMode, type LabelOptions, type Layout, LayoutConstraint, LegacyTimer, type LocaleSvg, type M2ColorfulPath, type M2DragEvent, type M2Event, type M2EventListener, M2EventType, type M2Image, M2ImageStatus, type M2KeyboardEvent, M2Node, type M2NodeAddChildEvent, type M2NodeConstructor, type M2NodeEvent, type M2NodeEventListener, M2NodeFactory, type M2NodeNewEvent, type M2NodeOptions, type M2NodePropertyChangeEvent, type M2NodeRemoveChildEvent, M2NodeType, type M2Path, type M2PointerEvent, type M2Sound, M2SoundStatus, M2c2KitHelpers, MoveAction, type MoveActionOptions, MutablePath, NoneTransition, PlayAction, type PlayActionOptions, type Plugin, type PluginEvent, type Point, RandomDraws, type RectOptions, RepeatAction, RepeatForeverAction, type RgbaColor, RotateAction, ScaleAction, type ScaleActionOptions, Scene, type SceneOptions, type ScenePresentEvent, SceneTransition, type ScoringSchema, SequenceAction, Shape, type ShapeOptions, ShapeType, type Size, SlideTransition, type SlideTransitionOptions, type SoundAsset, SoundManager, SoundPlayer, type SoundPlayerOptions, SoundRecorder, type SoundRecorderOptions, type SoundRecorderResults, Sprite, type SpriteOptions, Story, type StoryOptions, type StringInterpolationMap, type TapEvent, type TextAndFont, TextLine, type TextLineOptions, type TextLocalizationResult, type TextOptions, type TextWithFontCustomization, Timer, Transition, TransitionDirection, TransitionType, type Translation, type TranslationConfiguration, type TranslationOptions, type TrialData, type TrialSchema, Uuid, WaitAction, type WaitActionOptions, WebColors, WebGlInfo, handleInterfaceOptions };
5370
+ export { Action, ActivityType, CanvasKitHelpers, ColorfulMutablePath, Composite, Constants, ConstraintType, CustomAction, Dimensions, Easings, Equal, Equals, EventStore, EventStoreMode, FadeAlphaAction, FontManager, Game, GroupAction, I18n, ImageManager, Label, LabelHorizontalAlignmentMode, LayoutConstraint, LegacyTimer, M2EventType, M2ImageStatus, M2Node, M2NodeFactory, M2NodeType, M2SoundStatus, M2c2KitHelpers, MoveAction, MutablePath, NoneTransition, PlayAction, RandomDraws, RepeatAction, RepeatForeverAction, RotateAction, ScaleAction, Scene, SceneTransition, SequenceAction, Shape, ShapeType, SlideTransition, SoundManager, SoundPlayer, SoundRecorder, Sprite, Story, TextLine, Timer, Transition, TransitionDirection, TransitionType, Uuid, WaitAction, WebColors, WebGlInfo, handleInterfaceOptions };
5371
+ export type { Activity, ActivityCallbacks, ActivityEvent, ActivityEventListener, ActivityKeyValueData, ActivityLifecycleEvent, ActivityResultsEvent, BrowserImage, BrowserImageDataReadyEvent, CallbackOptions, CompositeEvent, CompositeOptions, Constraints, CustomActionOptions, DefaultParameter, DomPointerDownEvent, DrawableOptions, EasingFunction, FadeAlphaActionOptions, FontAsset, FontData, GameData, GameEvent, GameOptions, GameParameters, GlobalVariables, I18nDataReadyEvent, IDataStore, IDrawable, IText, LabelOptions, Layout, LocaleSvg, M2ColorfulPath, M2DragEvent, M2Event, M2EventListener, M2Image, M2KeyboardEvent, M2NodeAddChildEvent, M2NodeConstructor, M2NodeEvent, M2NodeEventListener, M2NodeNewEvent, M2NodeOptions, M2NodePropertyChangeEvent, M2NodeRemoveChildEvent, M2Path, M2PointerEvent, M2Sound, MoveActionOptions, PlayActionOptions, Plugin, PluginEvent, Point, RectOptions, RgbaColor, ScaleActionOptions, SceneOptions, ScenePresentEvent, ScoringSchema, ShapeOptions, Size, SlideTransitionOptions, SoundAsset, SoundPlayerOptions, SoundRecorderOptions, SoundRecorderResults, SpriteOptions, StoryOptions, StringInterpolationMap, TapEvent, TextAndFont, TextLineOptions, TextLocalizationResult, TextOptions, TextWithFontCustomization, Translation, TranslationConfiguration, TranslationOptions, TrialData, TrialSchema, WaitActionOptions };
package/dist/index.js CHANGED
@@ -4976,11 +4976,17 @@ const M2FontStatus = {
4976
4976
  /** Font has fully finished loading and is ready to use. */
4977
4977
  Ready: "Ready"};
4978
4978
 
4979
+ const TAG_NAME = {
4980
+ UNDERLINE: "u",
4981
+ ITALIC: "i",
4982
+ BOLD: "b"
4983
+ };
4979
4984
  class Label extends M2Node {
4980
4985
  /**
4981
4986
  * Single or multi-line text formatted and rendered on the screen.
4982
4987
  *
4983
- * @remarks Label (in contrast to TextLine) has enhanced text support for line wrapping, centering/alignment, and background colors.
4988
+ * @remarks Label (in contrast to TextLine) has enhanced text support for
4989
+ * line wrapping, centering/alignment, and background colors.
4984
4990
  *
4985
4991
  * @param options - {@link LabelOptions}
4986
4992
  */
@@ -5005,6 +5011,10 @@ class Label extends M2Node {
5005
5011
  // public getter/setter is below
5006
5012
  this._localize = true;
5007
5013
  this.localizedFontNames = [];
5014
+ this.plainText = "";
5015
+ this.styleSegments = [];
5016
+ this.underlinedRanges = [];
5017
+ this.currentBuilderPosition = 0;
5008
5018
  handleInterfaceOptions(this, options);
5009
5019
  if (options.horizontalAlignmentMode) {
5010
5020
  this.horizontalAlignmentMode = options.horizontalAlignmentMode;
@@ -5056,14 +5066,14 @@ class Label extends M2Node {
5056
5066
  this.fontColor[2],
5057
5067
  this.fontColor[3]
5058
5068
  );
5059
- let textForParagraph;
5069
+ let textAfterLocalization;
5060
5070
  const i18n = this.game.i18n;
5061
5071
  if (i18n && this.localize !== false) {
5062
5072
  const textLocalization = i18n.getTextLocalization(
5063
5073
  this.text,
5064
5074
  this.interpolation
5065
5075
  );
5066
- textForParagraph = textLocalization.text;
5076
+ textAfterLocalization = textLocalization.text;
5067
5077
  this.localizedFontSize = textLocalization.fontSize;
5068
5078
  this.localizedFontName = textLocalization.fontName;
5069
5079
  this.localizedFontNames = textLocalization.fontNames ?? [];
@@ -5076,8 +5086,13 @@ class Label extends M2Node {
5076
5086
  );
5077
5087
  }
5078
5088
  } else {
5079
- textForParagraph = this.text;
5089
+ textAfterLocalization = this.text;
5080
5090
  }
5091
+ this.currentBuilderPosition = 0;
5092
+ this.underlinedRanges = [];
5093
+ const parsedText = this.parseFormattedText(textAfterLocalization);
5094
+ this.plainText = parsedText.plainText;
5095
+ this.styleSegments = parsedText.styleSegments;
5081
5096
  if (this.fontName && this.fontNames) {
5082
5097
  throw new Error("cannot specify both fontName and fontNames");
5083
5098
  }
@@ -5117,36 +5132,39 @@ class Label extends M2Node {
5117
5132
  } else {
5118
5133
  this.backgroundPaint.setColor(this.canvasKit.Color(0, 0, 0, 0));
5119
5134
  }
5120
- this.builder.pushPaintStyle(
5121
- {
5122
- fontFamilies: requiredFonts.map((font) => font.fontName),
5123
- fontSize: (this.localizedFontSize ?? this.fontSize) * m2c2Globals.canvasScale,
5124
- // set default values for below properties as well.
5125
- fontStyle: {
5126
- weight: this.canvasKit.FontWeight.Normal,
5127
- width: this.canvasKit.FontWidth.Normal,
5128
- slant: this.canvasKit.FontSlant.Oblique
5129
- },
5130
- // Normal font style
5131
- decoration: 0,
5132
- // No decoration
5133
- decorationThickness: 1,
5134
- // Default decoration thickness
5135
- decorationStyle: this.canvasKit.DecorationStyle.Solid,
5136
- // Solid decoration style
5137
- heightMultiplier: -1,
5138
- // Providing -1, rather than 1.0, gives default height multiplier
5139
- halfLeading: false,
5140
- // Default half leading
5141
- letterSpacing: 0,
5142
- // Default letter spacing
5143
- wordSpacing: 0
5144
- // Default word spacing
5135
+ if (!this._underlinePaint) {
5136
+ this._underlinePaint = new this.canvasKit.Paint();
5137
+ }
5138
+ this.underlinePaint.setColor(this.fontPaint.getColor());
5139
+ this.underlinePaint.setAlphaf(this.absoluteAlpha);
5140
+ this.underlinePaint.setStyle(this.canvasKit.PaintStyle.Fill);
5141
+ this.underlinePaint.setAntiAlias(true);
5142
+ const defaultStyle = {
5143
+ fontFamilies: requiredFonts.map((font) => font.fontName),
5144
+ fontSize: (this.localizedFontSize ?? this.fontSize) * m2c2Globals.canvasScale,
5145
+ fontStyle: {
5146
+ weight: this.canvasKit.FontWeight.Normal,
5147
+ width: this.canvasKit.FontWidth.Normal,
5148
+ slant: this.canvasKit.FontSlant.Upright
5145
5149
  },
5150
+ // Normal font style
5151
+ decoration: 0,
5152
+ // No decoration
5153
+ decorationThickness: 1,
5154
+ // Default decoration thickness
5155
+ decorationStyle: this.canvasKit.DecorationStyle.Solid,
5156
+ heightMultiplier: -1,
5157
+ // Providing -1, rather than 1.0, gives default height multiplier
5158
+ halfLeading: false,
5159
+ letterSpacing: 0,
5160
+ wordSpacing: 0
5161
+ };
5162
+ this.builder.pushPaintStyle(
5163
+ defaultStyle,
5146
5164
  this.fontPaint,
5147
5165
  this.backgroundPaint
5148
5166
  );
5149
- this.builder.addText(textForParagraph);
5167
+ this.addTextWithStylesToParagraphBuilder(defaultStyle);
5150
5168
  if (this.paragraph) {
5151
5169
  this.paragraph.delete();
5152
5170
  }
@@ -5174,6 +5192,185 @@ class Label extends M2Node {
5174
5192
  this.size.height = this.paragraph.getHeight() / m2c2Globals.canvasScale;
5175
5193
  this.needsInitialization = false;
5176
5194
  }
5195
+ /**
5196
+ * Parses text with formatting tags and returns plain text and style segments.
5197
+ * Supports <b> for bold, <i> for italics, and <u> for underline.
5198
+ * Properly handles nested tags like <b><u>bold and underlined</u></b>.
5199
+ * Throws errors for malformed tags, but treats unknown tags as plain text.
5200
+ *
5201
+ * @param text - The text with formatting tags
5202
+ * @returns The parsed text result
5203
+ * @throws Error if tags are improperly nested or unclosed
5204
+ */
5205
+ parseFormattedText(text) {
5206
+ let plainText = "";
5207
+ const styleChanges = [];
5208
+ const tagStack = [];
5209
+ const validTags = /* @__PURE__ */ new Set([
5210
+ TAG_NAME.UNDERLINE,
5211
+ TAG_NAME.BOLD,
5212
+ TAG_NAME.ITALIC
5213
+ ]);
5214
+ const tagPattern = /<\/?([^>]+)>/g;
5215
+ let lastIndex = 0;
5216
+ let plainIndex = 0;
5217
+ let match = null;
5218
+ const getPosition = (index) => {
5219
+ let line = 1;
5220
+ let column = 1;
5221
+ for (let i = 0; i < index; i++) {
5222
+ if (text[i] === "\n") {
5223
+ line++;
5224
+ column = 1;
5225
+ } else {
5226
+ column++;
5227
+ }
5228
+ }
5229
+ return `line ${line}, column ${column}`;
5230
+ };
5231
+ while ((match = tagPattern.exec(text)) !== null) {
5232
+ const fullTag = match[0];
5233
+ const tagName = match[1];
5234
+ const isClosing = fullTag.charAt(1) === "/";
5235
+ const position = getPosition(match.index);
5236
+ if (!validTags.has(tagName)) {
5237
+ const beforeTag2 = text.substring(lastIndex, match.index);
5238
+ plainText += beforeTag2;
5239
+ plainIndex += beforeTag2.length;
5240
+ plainText += fullTag;
5241
+ plainIndex += fullTag.length;
5242
+ lastIndex = match.index + fullTag.length;
5243
+ continue;
5244
+ }
5245
+ const beforeTag = text.substring(lastIndex, match.index);
5246
+ plainText += beforeTag;
5247
+ plainIndex += beforeTag.length;
5248
+ if (isClosing) {
5249
+ if (tagStack.length === 0) {
5250
+ throw new Error(
5251
+ `Label has closing tag </${tagName}> at ${position} without matching opening tag. Text is: ${text}`
5252
+ );
5253
+ }
5254
+ const lastOpenTag = tagStack.pop();
5255
+ if (lastOpenTag !== tagName) {
5256
+ throw new Error(
5257
+ `Label has improperly nested tags at ${position}. Expected </${lastOpenTag}> but found </${tagName}>. Tags must be properly nested. Text is: ${text}`
5258
+ );
5259
+ }
5260
+ styleChanges.push({
5261
+ position: plainIndex,
5262
+ style: tagName,
5263
+ isStart: false
5264
+ });
5265
+ } else {
5266
+ tagStack.push(tagName);
5267
+ styleChanges.push({
5268
+ position: plainIndex,
5269
+ style: tagName,
5270
+ isStart: true
5271
+ });
5272
+ }
5273
+ lastIndex = match.index + fullTag.length;
5274
+ }
5275
+ plainText += text.substring(lastIndex);
5276
+ if (tagStack.length > 0) {
5277
+ throw new Error(
5278
+ `Label has unclosed format tags: <${tagStack.join(">, <")}>. All tags must be closed. Text is: ${text}`
5279
+ );
5280
+ }
5281
+ const activeStyles = /* @__PURE__ */ new Set();
5282
+ const segments = [];
5283
+ let lastPosition = 0;
5284
+ styleChanges.sort((a, b) => a.position - b.position);
5285
+ for (const change of styleChanges) {
5286
+ if (change.position > lastPosition && activeStyles.size > 0) {
5287
+ segments.push({
5288
+ start: lastPosition,
5289
+ end: change.position,
5290
+ styles: new Set(activeStyles)
5291
+ // Create a copy of the current styles
5292
+ });
5293
+ }
5294
+ if (change.isStart) {
5295
+ activeStyles.add(change.style);
5296
+ } else {
5297
+ activeStyles.delete(change.style);
5298
+ }
5299
+ lastPosition = change.position;
5300
+ }
5301
+ return {
5302
+ plainText,
5303
+ styleSegments: segments
5304
+ };
5305
+ }
5306
+ addTextWithStylesToParagraphBuilder(defaultStyle) {
5307
+ if (this.styleSegments.length === 0) {
5308
+ if (!this.builder) {
5309
+ throw new Error("ParagraphBuilder is undefined");
5310
+ }
5311
+ this.builder.addText(this.plainText);
5312
+ } else {
5313
+ let lastIndex = 0;
5314
+ for (const segment of this.styleSegments) {
5315
+ if (segment.start > lastIndex) {
5316
+ this.addTextWithStyle(
5317
+ this.plainText.substring(lastIndex, segment.start),
5318
+ defaultStyle
5319
+ );
5320
+ }
5321
+ const styleModifiers = {};
5322
+ if (segment.styles.has(TAG_NAME.BOLD)) {
5323
+ styleModifiers.fontStyle = {
5324
+ ...defaultStyle.fontStyle,
5325
+ weight: this.canvasKit.FontWeight.Bold
5326
+ };
5327
+ }
5328
+ if (segment.styles.has(TAG_NAME.ITALIC)) {
5329
+ styleModifiers.fontStyle = {
5330
+ ...styleModifiers.fontStyle || defaultStyle.fontStyle,
5331
+ slant: this.canvasKit.FontSlant.Italic
5332
+ };
5333
+ }
5334
+ if (segment.styles.has(TAG_NAME.UNDERLINE)) {
5335
+ styleModifiers.underline = true;
5336
+ }
5337
+ this.addTextWithStyle(
5338
+ this.plainText.substring(segment.start, segment.end),
5339
+ defaultStyle,
5340
+ styleModifiers
5341
+ );
5342
+ lastIndex = segment.end;
5343
+ }
5344
+ if (lastIndex < this.plainText.length) {
5345
+ this.addTextWithStyle(
5346
+ this.plainText.substring(lastIndex),
5347
+ defaultStyle
5348
+ );
5349
+ }
5350
+ }
5351
+ }
5352
+ // Helper to apply style and add text
5353
+ addTextWithStyle(text, defaultStyle, styleModifiers = {}) {
5354
+ if (!text) return;
5355
+ const style = {
5356
+ ...defaultStyle,
5357
+ ...styleModifiers
5358
+ };
5359
+ if (styleModifiers.underline) {
5360
+ const currentPosition = this.currentBuilderPosition;
5361
+ this.underlinedRanges.push({
5362
+ start: currentPosition,
5363
+ end: currentPosition + text.length
5364
+ });
5365
+ }
5366
+ if (!this.builder) {
5367
+ throw new Error("ParagraphBuilder is undefined");
5368
+ }
5369
+ this.builder.pushPaintStyle(style, this.fontPaint, this.backgroundPaint);
5370
+ this.builder.addText(text);
5371
+ this.currentBuilderPosition += text.length;
5372
+ this.builder.pop();
5373
+ }
5177
5374
  /**
5178
5375
  * Determines the M2Font objects that need to be ready in order to draw
5179
5376
  * the Label.
@@ -5214,7 +5411,9 @@ class Label extends M2Node {
5214
5411
  this.builder,
5215
5412
  this._fontPaint,
5216
5413
  // use backing field since it may be undefined
5217
- this._backgroundPaint
5414
+ this._backgroundPaint,
5415
+ // use backing field since it may be undefined
5416
+ this._underlinePaint
5218
5417
  // use backing field since it may be undefined
5219
5418
  ]);
5220
5419
  }
@@ -5395,6 +5594,15 @@ class Label extends M2Node {
5395
5594
  set fontPaint(fontPaint) {
5396
5595
  this._fontPaint = fontPaint;
5397
5596
  }
5597
+ get underlinePaint() {
5598
+ if (!this._underlinePaint) {
5599
+ throw new Error("underlinePaint cannot be undefined");
5600
+ }
5601
+ return this._underlinePaint;
5602
+ }
5603
+ set underlinePaint(underlinePaint) {
5604
+ this._underlinePaint = underlinePaint;
5605
+ }
5398
5606
  /**
5399
5607
  * Duplicates a node using deep copy.
5400
5608
  *
@@ -5442,10 +5650,42 @@ class Label extends M2Node {
5442
5650
  throw new Error("no paragraph");
5443
5651
  }
5444
5652
  canvas.drawParagraph(this.paragraph, x, y);
5653
+ if (this.underlinedRanges.length > 0) {
5654
+ this.drawUnderlines(canvas, this.paragraph, x, y);
5655
+ }
5445
5656
  canvas.restore();
5446
5657
  }
5447
5658
  super.drawChildren(canvas);
5448
5659
  }
5660
+ drawUnderlines(canvas, paragraph, x, y) {
5661
+ const drawScale = m2c2Globals.canvasScale / this.absoluteScale;
5662
+ for (const range of this.underlinedRanges) {
5663
+ const positions = paragraph.getRectsForRange(
5664
+ range.start,
5665
+ range.end,
5666
+ this.canvasKit.RectHeightStyle.Max,
5667
+ this.canvasKit.RectWidthStyle.Tight
5668
+ );
5669
+ for (const rect of positions) {
5670
+ const rectHeight = rect.rect[3] - rect.rect[1];
5671
+ const underlineVerticalOffset = -rectHeight * 0.08 * drawScale * this.absoluteScale;
5672
+ const lineThickness = rectHeight * 0.04 * drawScale * this.absoluteScale;
5673
+ canvas.drawRect(
5674
+ [
5675
+ x + rect.rect[0],
5676
+ // left
5677
+ y + rect.rect[3] + underlineVerticalOffset,
5678
+ // bottom of text + offset
5679
+ x + rect.rect[2],
5680
+ // right
5681
+ y + rect.rect[3] + underlineVerticalOffset + lineThickness
5682
+ // bottom of text + offset + thickness
5683
+ ],
5684
+ this.underlinePaint
5685
+ );
5686
+ }
5687
+ }
5688
+ }
5449
5689
  warmup(canvas) {
5450
5690
  const i18n = this.game.i18n;
5451
5691
  if (i18n && this.localize !== false) {
@@ -7253,7 +7493,7 @@ function getDefaultExportFromCjs (x) {
7253
7493
  }
7254
7494
 
7255
7495
  function getAugmentedNamespace(n) {
7256
- if (n.__esModule) return n;
7496
+ if (Object.prototype.hasOwnProperty.call(n, '__esModule')) return n;
7257
7497
  var f = n.default;
7258
7498
  if (typeof f == "function") {
7259
7499
  var a = function a () {
@@ -9220,11 +9460,18 @@ class Game {
9220
9460
  /**
9221
9461
  * Adds a scene to the game.
9222
9462
  *
9223
- * @remarks A scene, and its children nodes, cannot be presented unless it has been added to the game object.
9463
+ * @remarks A scene, and its children nodes, cannot be presented unless it
9464
+ * has been added to the game object. A scene can be added to the game
9465
+ * only once.
9224
9466
  *
9225
9467
  * @param scene
9226
9468
  */
9227
9469
  addScene(scene) {
9470
+ if (this.scenes.includes(scene)) {
9471
+ console.warn(
9472
+ `Game.addScene(): scene ${scene.toString()} has already been added to the game. This will cause unpredictable behavior. This warning will become an error in a future release.`
9473
+ );
9474
+ }
9228
9475
  scene.game = this;
9229
9476
  scene.needsInitialization = true;
9230
9477
  this.scenes.push(scene);
@@ -12271,7 +12518,7 @@ class Story {
12271
12518
  }
12272
12519
  }
12273
12520
 
12274
- console.log("\u26AA @m2c2kit/core version 0.3.28 (622f7241)");
12521
+ console.log("\u26AA @m2c2kit/core version 0.3.29 (d1ad307f)");
12275
12522
 
12276
12523
  export { Action, ActivityType, CanvasKitHelpers, ColorfulMutablePath, Composite, Constants, ConstraintType, CustomAction, Dimensions, Easings, Equal, Equals, EventStore, EventStoreMode, FadeAlphaAction, FontManager, Game, GroupAction, I18n, ImageManager, Label, LabelHorizontalAlignmentMode, LayoutConstraint, LegacyTimer, M2EventType, M2ImageStatus, M2Node, M2NodeFactory, M2NodeType, M2SoundStatus, M2c2KitHelpers, MoveAction, MutablePath, NoneTransition, PlayAction, RandomDraws, RepeatAction, RepeatForeverAction, RotateAction, ScaleAction, Scene, SceneTransition, SequenceAction, Shape, ShapeType, SlideTransition, SoundManager, SoundPlayer, SoundRecorder, Sprite, Story, TextLine, Timer, Transition, TransitionDirection, TransitionType, Uuid, WaitAction, WebColors, WebGlInfo, handleInterfaceOptions };
12277
12524
  //# sourceMappingURL=index.js.map