@itwin/core-common
Version:
iTwin.js components common to frontend and backend
359 lines • 14.4 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Annotation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextBlock = exports.Paragraph = exports.LineBreakRun = exports.FractionRun = exports.TextRun = exports.Run = exports.TextBlockComponent = void 0;
/** Abstract representation of any of the building blocks that make up a [[TextBlock]] document - namely [[Run]]s, [[Paragraph]]s, and [[TextBlock]] itself.
* Each component can specify a [[TextStyle]] that formats its contents and optional [[styleOverrides]] to customize that formatting.
* @beta
*/
class TextBlockComponent {
_styleName;
_styleOverrides;
/** @internal */
constructor(props) {
this._styleName = props.styleName;
this._styleOverrides = { ...props.styleOverrides };
}
/** The name of the [[TextStyle]] that provides the base formatting for the contents of this component.
* @note Assigning to this property is equivalent to calling [[applyStyle]] with default [[ApplyTextStyleOptions]], which propagates the style change to all of
* the components sub-components and clears any [[styleOverrides]].
*/
get styleName() {
return this._styleName;
}
set styleName(styleName) {
this.applyStyle(styleName);
}
/** Deviations in individual properties of the [[TextStyle]] specified by [[styleName]].
* For example, if the style uses the "Arial" font, you can override that by settings `styleOverrides.fontName` to "Comic Sans".
* @see [[clearStyleOverrides]] to reset this to an empty object.
*/
get styleOverrides() {
return this._styleOverrides;
}
set styleOverrides(overrides) {
this._styleOverrides = { ...overrides };
}
/** Reset any [[styleOverrides]] applied to this component's [[TextStyle]]. */
clearStyleOverrides() {
this.styleOverrides = {};
}
/** Apply the [[TextStyle]] specified by `styleName` to this component, optionally preserving [[styleOverrides]] and/or preventing propagation to sub-components. */
applyStyle(styleName, options) {
this._styleName = styleName;
if (!(options?.preserveOverrides)) {
this.clearStyleOverrides();
}
}
/** Returns true if [[styleOverrides]] specifies any deviations from this component's base [[TextStyle]]. */
get overridesStyle() {
return Object.keys(this.styleOverrides).length > 0;
}
/** Convert this component to its JSON representation. */
toJSON() {
return {
styleName: this.styleName,
styleOverrides: { ...this.styleOverrides },
};
}
/** Returns true if `this` is equivalent to `other`. */
equals(other) {
const myKeys = Object.keys(this.styleOverrides);
const yrKeys = Object.keys(other._styleOverrides);
if (this.styleName !== other.styleName || myKeys.length !== yrKeys.length) {
return false;
}
for (const name of myKeys) {
const key = name;
if (this.styleOverrides[key] !== other.styleOverrides[key]) {
return false;
}
}
return true;
}
}
exports.TextBlockComponent = TextBlockComponent;
/** A sequence of characters within a [[Paragraph]] that share a single style. Runs are the leaf nodes of a [[TextBlock]] document. When laid out for display, a single run may span
* multiple lines, but it will never contain different styling.
* Use the `type` field to discriminate between the different kinds of runs.
* @beta
*/
var Run;
(function (Run) {
/** Create a run from its JSON representation.
* @see [[TextRun.create]], [[FractionRun.create]], and [[LineBreakRun.create]] to create a run directly.
*/
function fromJSON(props) {
switch (props.type) {
case "text": return TextRun.create(props);
case "fraction": return FractionRun.create(props);
case "linebreak": return LineBreakRun.create(props);
}
}
Run.fromJSON = fromJSON;
})(Run || (exports.Run = Run = {}));
/** The most common type of [[Run]], containing a sequence of characters to be displayed using a single style.
* @beta
*/
class TextRun extends TextBlockComponent {
/** Discriminator field for the [[Run]] union. */
type = "text";
/** The sequence of characters to be displayed by the run. */
content;
/** Whether to display [[content]] as a subscript, superscript, or normally. */
baselineShift;
constructor(props) {
super(props);
this.content = props.content ?? "";
this.baselineShift = props.baselineShift ?? "none";
}
clone() {
return new TextRun(this.toJSON());
}
toJSON() {
return {
...super.toJSON(),
type: "text",
content: this.content,
baselineShift: this.baselineShift,
};
}
static create(props) {
return new TextRun(props);
}
/** Simply returns [[content]]. */
stringify() {
return this.content;
}
equals(other) {
return other instanceof TextRun && this.content === other.content && this.baselineShift === other.baselineShift && super.equals(other);
}
}
exports.TextRun = TextRun;
/** A [[Run]] containing a numeric ratio to be displayed as a numerator and denominator separated by a horizontal or diagonal bar.
* @note The [[numerator]] and [[denominator]] are stored as strings. They are not technically required to contain a numeric representation.
* @beta
*/
class FractionRun extends TextBlockComponent {
/** Discriminator field for the [[Run]] union. */
type = "fraction";
/** The fraction's numerator. */
numerator;
/** The fraction's denominator. */
denominator;
constructor(props) {
super(props);
this.numerator = props.numerator ?? "";
this.denominator = props.denominator ?? "";
}
toJSON() {
return {
...super.toJSON(),
type: "fraction",
numerator: this.numerator,
denominator: this.denominator,
};
}
clone() {
return new FractionRun(this.toJSON());
}
static create(props) {
return new FractionRun(props);
}
/** Formats the fraction as a string with the [[numerator]] and [[denominator]] separated by [[TextBlockStringifyOptions.fractionSeparator]]. */
stringify(options) {
const sep = options?.fractionSeparator ?? "/";
return `${this.numerator}${sep}${this.denominator}`;
}
equals(other) {
return other instanceof FractionRun && this.numerator === other.numerator && this.denominator === other.denominator && super.equals(other);
}
}
exports.FractionRun = FractionRun;
/** A [[Run]] that represents the end of a line of text within a [[Paragraph]]. It contains no content of its own - it simply causes subsequent content to display on a new line.
* @beta
*/
class LineBreakRun extends TextBlockComponent {
/** Discriminator field for the [[Run]] union. */
type = "linebreak";
constructor(props) {
super(props);
}
toJSON() {
return {
...super.toJSON(),
type: "linebreak",
};
}
static create(props) {
return new LineBreakRun(props);
}
clone() {
return new LineBreakRun(this.toJSON());
}
/** Simply returns [[TextBlockStringifyOptions.lineBreak]]. */
stringify(options) {
return options?.lineBreak ?? " ";
}
equals(other) {
return other instanceof LineBreakRun && super.equals(other);
}
}
exports.LineBreakRun = LineBreakRun;
/** A collection of [[Run]]s within a [[TextBlock]]. Each paragraph within a text block is laid out on a separate line.
* @beta
*/
class Paragraph extends TextBlockComponent {
/** The runs within the paragraph. You can modify the contents of this array to change the content of the paragraph. */
runs;
constructor(props) {
super(props);
this.runs = props.runs?.map((run) => Run.fromJSON(run)) ?? [];
}
toJSON() {
return {
...super.toJSON(),
runs: this.runs.map((run) => run.toJSON()),
};
}
/** Create a paragraph from its JSON representation. */
static create(props) {
return new Paragraph(props);
}
clone() {
return new Paragraph(this.toJSON());
}
/** Apply the specified style to this [[Paragraph]], and - unless [[ApplyTextStyleOptions.preventPropagation]] is `true` - to all of its [[runs]]. */
applyStyle(styleName, options) {
super.applyStyle(styleName, options);
if (!(options?.preventPropagation)) {
for (const run of this.runs) {
run.applyStyle(styleName, options);
}
}
}
/** Compute a string representation of this paragraph by concatenating the string representations of all of its [[runs]]. */
stringify(options) {
return this.runs.map((x) => x.stringify(options)).join("");
}
equals(other) {
if (!(other instanceof Paragraph)) {
return false;
}
if (this.runs.length !== other.runs.length || !super.equals(other)) {
return false;
}
return this.runs.every((run, index) => run.equals(other.runs[index]));
}
}
exports.Paragraph = Paragraph;
;
/** Represents a formatted text document consisting of a series of [[Paragraph]]s, each laid out on a separate line and containing their own content in the form of [[Run]]s.
* You can change the content of the document by directly modifying the contents of its [[paragraphs]], or via [[appendParagraph]] and [[appendRun]].
* No word-wrapping is applied to the document unless a [[width]] greater than zero is specified.
* @see [[TextAnnotation]] to position a text block as an annotation in 2d or 3d space.
* @beta
*/
class TextBlock extends TextBlockComponent {
/** The width of the document in meters. Lines that would exceed this width are instead wrapped around to the next line if possible.
* A value less than or equal to zero indicates no wrapping is to be applied.
* Default: 0
*/
width;
/** The alignment of the document's content. */
justification;
/** The margins of the document. */
margins;
/** The ordered list of paragraphs within the document. */
paragraphs;
constructor(props) {
super(props);
this.width = props.width ?? 0;
this.justification = props.justification ?? "left";
// Assign default margins if not provided
this.margins = {
left: props.margins?.left ?? 0,
right: props.margins?.right ?? 0,
top: props.margins?.top ?? 0,
bottom: props.margins?.bottom ?? 0,
};
this.paragraphs = props.paragraphs?.map((x) => Paragraph.create(x)) ?? [];
}
toJSON() {
return {
...super.toJSON(),
width: this.width,
justification: this.justification,
margins: this.margins,
paragraphs: this.paragraphs.map((x) => x.toJSON()),
};
}
/** Create a text block from its JSON representation. */
static create(props) {
return new TextBlock(props);
}
/** Create an empty text block containing no [[paragraphs]] and an empty [[styleName]]. */
static createEmpty() {
return TextBlock.create({ styleName: "" });
}
/** Returns true if every paragraph in this text block is empty. */
get isEmpty() {
return this.paragraphs.every((p) => p.runs.length === 0);
}
clone() {
return new TextBlock(this.toJSON());
}
/** Apply the specified style to this block and - unless [[ApplyTextStyleOptions.preventPropagation]] is `true` - to all of its [[paragraphs]]. */
applyStyle(styleName, options) {
super.applyStyle(styleName, options);
if (!(options?.preventPropagation)) {
for (const paragraph of this.paragraphs) {
paragraph.applyStyle(styleName, options);
}
}
}
/** Compute a string representation of the document's contents by concatenating the string representations of each of its [[paragraphs]], separated by [[TextBlockStringifyOptions.paragraphBreak]]. */
stringify(options) {
return this.paragraphs.map((x) => x.stringify(options)).join(options?.paragraphBreak ?? " ");
}
/** Add and return a new paragraph.
* If [[paragraphs]] is not empty, the style and overrides of the last [[Paragraph]] in the block will be applied to the new paragraph; otherwise,
* the paragraph will inherit this block's style with no overrides.
*/
appendParagraph() {
const seed = this.paragraphs[0];
const paragraph = Paragraph.create({
styleName: seed?.styleName ?? this.styleName,
styleOverrides: seed?.styleOverrides ?? undefined,
});
this.paragraphs.push(paragraph);
return paragraph;
}
/** Append a run to the last [[Paragraph]] in this block.
* If the block contains no [[paragraphs]], a new one will first be created using [[appendParagraph]].
*/
appendRun(run) {
const paragraph = this.paragraphs[this.paragraphs.length - 1] ?? this.appendParagraph();
paragraph.runs.push(run);
}
equals(other) {
if (!(other instanceof TextBlock)) {
return false;
}
if (this.width !== other.width || this.justification !== other.justification || this.paragraphs.length !== other.paragraphs.length) {
return false;
}
const marginsAreEqual = Object.entries(this.margins).every(([key, value]) => value === other.margins[key]);
if (!marginsAreEqual)
return false;
return this.paragraphs.every((paragraph, index) => paragraph.equals(other.paragraphs[index]));
}
}
exports.TextBlock = TextBlock;
//# sourceMappingURL=TextBlock.js.map