UNPKG

@itwin/core-backend

Version:
640 lines • 28.2 kB
"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 Elements */ Object.defineProperty(exports, "__esModule", { value: true }); exports.AnnotationTextStyle = exports.TEXT_STYLE_SETTINGS_JSON_VERSION = exports.TextAnnotation3d = exports.TextAnnotation2d = exports.TEXT_ANNOTATION_JSON_VERSION = void 0; exports.parseTextAnnotationData = parseTextAnnotationData; const core_common_1 = require("@itwin/core-common"); const Element_1 = require("../Element"); const core_bentley_1 = require("@itwin/core-bentley"); const TextBlockLayout_1 = require("./TextBlockLayout"); const TextAnnotationGeometry_1 = require("./TextAnnotationGeometry"); const ElementDrivesTextAnnotation_1 = require("./ElementDrivesTextAnnotation"); const semver = require("semver"); /** The version of the JSON stored in `TextAnnotation2d/3dProps.textAnnotationData` used by the code. * Uses the same semantics as [ECVersion]($ecschema-metadata). * @internal */ exports.TEXT_ANNOTATION_JSON_VERSION = "1.0.0"; function validateAndMigrateVersionedJSON(json, currentVersion, migrate) { let parsed; try { parsed = JSON.parse(json); } catch { return undefined; } const version = parsed.version; if (typeof version !== "string" || !semver.valid(version)) throw new Error("JSON version is missing or invalid."); if (typeof parsed.data !== "object" || parsed.data === null) throw new Error("JSON data is missing or invalid."); // Newer if (semver.gt(version, currentVersion)) throw new Error(`JSON version ${parsed.version} is newer than supported version ${currentVersion}. Application update required to understand data.`); // Older if (semver.lt(version, currentVersion)) { parsed.data = migrate(parsed); parsed.version = currentVersion; } return parsed; } function migrateTextAnnotationData(oldData) { if (oldData.version === exports.TEXT_ANNOTATION_JSON_VERSION) return oldData.data; // Place migration logic here. throw new Error(`Migration for textAnnotationData from version ${oldData.version} to ${exports.TEXT_ANNOTATION_JSON_VERSION} failed.`); } /** Parses, validates, and potentially migrates the text annotation data from a JSON string. * @internal */ function parseTextAnnotationData(json) { if (!json) return undefined; return validateAndMigrateVersionedJSON(json, exports.TEXT_ANNOTATION_JSON_VERSION, migrateTextAnnotationData); } function getElementGeometryBuilderParams(iModel, modelId, categoryId, _placementProps, annotationProps, textStyleId, _subCategory) { const textBlock = core_common_1.TextAnnotation.fromJSON(annotationProps).textBlock; const textStyleResolver = new TextBlockLayout_1.TextStyleResolver({ textBlock, textStyleId: textStyleId ?? "", iModel }); const layout = (0, TextBlockLayout_1.layoutTextBlock)({ iModel, textBlock, textStyleResolver }); const builder = new core_common_1.ElementGeometry.Builder(); let scaleFactor = 1; const element = iModel.elements.getElement(modelId); if (element instanceof Element_1.Drawing) scaleFactor = element.scaleFactor; (0, TextAnnotationGeometry_1.appendTextAnnotationGeometry)({ layout, textStyleResolver, scaleFactor, annotationProps: annotationProps ?? {}, builder, categoryId }); return { entryArray: builder.entries }; } /** An element that displays textual content within a 2d model. * The text is stored as a [TextAnnotation]($common) from which the element's [geometry]($docs/learning/common/GeometryStream.md) and [Placement]($common) are computed. * @see [[setAnnotation]] to change the textual content. * @public @preview */ class TextAnnotation2d extends Element_1.AnnotationElement2d /* implements ITextAnnotation */ { /** @internal */ static get className() { return "TextAnnotation2d"; } /** * The default [[AnnotationTextStyle]] used by the TextAnnotation2d. * @beta */ defaultTextStyle; /** The data associated with the text annotation. */ _textAnnotationProps; /** Extract the textual content, if present. * @see [[setAnnotation]] to change it. */ getAnnotation() { return this._textAnnotationProps ? core_common_1.TextAnnotation.fromJSON(this._textAnnotationProps) : undefined; } /** Change the textual content of the `TextAnnotation2d`. * @see [[getAnnotation]] to extract the current annotation. * @param annotation The new annotation */ setAnnotation(annotation) { this._textAnnotationProps = annotation.toJSON(); } constructor(props, iModel) { super(props, iModel); if (props.defaultTextStyle) { this.defaultTextStyle = new ElementDrivesTextAnnotation_1.TextAnnotationUsesTextStyleByDefault(props.defaultTextStyle.id); } this._textAnnotationProps = parseTextAnnotationData(props.textAnnotationData)?.data; } /** Creates a new instance of `TextAnnotation2d` from its JSON representation. */ static fromJSON(props, iModel) { return new TextAnnotation2d(props, iModel); } /** * Converts the current `TextAnnotation2d` instance to its JSON representation. * It also computes the `elementGeometryBuilderParams` property used to create the GeometryStream. * @inheritdoc */ toJSON() { const props = super.toJSON(); props.textAnnotationData = this._textAnnotationProps ? JSON.stringify({ version: exports.TEXT_ANNOTATION_JSON_VERSION, data: this._textAnnotationProps }) : undefined; if (this._textAnnotationProps) { props.elementGeometryBuilderParams = getElementGeometryBuilderParams(this.iModel, this.model, this.category, this.placement, this._textAnnotationProps, this.defaultTextStyle ? this.defaultTextStyle.id : undefined); } return props; } /** Creates a new `TextAnnotation2d` instance with the specified properties. * @param iModelDb The iModel. * @param arg The arguments for creating the TextAnnotation2d. * @beta */ static create(iModelDb, arg) { const elementProps = { classFullName: this.classFullName, textAnnotationData: arg.textAnnotationProps ? JSON.stringify({ version: exports.TEXT_ANNOTATION_JSON_VERSION, data: arg.textAnnotationProps }) : undefined, defaultTextStyle: arg.defaultTextStyleId ? new ElementDrivesTextAnnotation_1.TextAnnotationUsesTextStyleByDefault(arg.defaultTextStyleId).toJSON() : undefined, placement: arg.placement, model: arg.model, category: arg.category, code: arg.code ?? core_common_1.Code.createEmpty(), }; return new this(elementProps, iModelDb); } /** * Updates the geometry of the TextAnnotation2d on insert and validates version. * @inheritdoc * @beta */ static onInsert(arg) { super.onInsert(arg); this.validateVersionAndUpdateGeometry(arg); } /** * Updates the geometry of the TextAnnotation2d on update and validates version. * @inheritdoc * @beta */ static onUpdate(arg) { super.onUpdate(arg); this.validateVersionAndUpdateGeometry(arg); } /** * Populates the `elementGeometryBuilderParams` property in the [TextAnnotation2dProps]($common). * Only does this if the `elementGeometryBuilderParams` is not already set and if there is actually a text annotation to produce geometry for. * Also, validates the version of the text annotation data and migrates it if necessary. * @beta */ static validateVersionAndUpdateGeometry(arg) { const props = arg.props; const textAnnotationData = parseTextAnnotationData(props.textAnnotationData); if (!props.elementGeometryBuilderParams && textAnnotationData) { props.elementGeometryBuilderParams = getElementGeometryBuilderParams(arg.iModel, props.model, props.category, props.placement ?? core_common_1.Placement2d.fromJSON(), textAnnotationData.data, props.defaultTextStyle?.id); } } /** * TextAnnotation2d custom HandledProps include 'textAnnotationData'. * @inheritdoc * @internal */ static _customHandledProps = [ { propertyName: "textAnnotationData", source: "Class" }, ]; /** * TextAnnotation2d deserializes 'textAnnotationData'. * @inheritdoc * @beta */ static deserialize(props) { const elProps = super.deserialize(props); const textAnnotationData = parseTextAnnotationData(props.row.textAnnotationData); if (textAnnotationData) { elProps.textAnnotationData = JSON.stringify(textAnnotationData); } return elProps; } /** * TextAnnotation2d serializes 'textAnnotationData'. * @inheritdoc * @beta */ static serialize(props, iModel) { const inst = super.serialize(props, iModel); if (props.textAnnotationData !== undefined) { inst.textAnnotationData = props.textAnnotationData; } return inst; } /** @internal */ getTextBlocks() { return getTextBlocks(this); } /** @internal */ updateTextBlocks(textBlocks) { return updateTextBlocks(this, textBlocks); } /** @internal */ static onInserted(arg) { super.onInserted(arg); ElementDrivesTextAnnotation_1.ElementDrivesTextAnnotation.updateFieldDependencies(arg.id, arg.iModel); } /** @internal */ static onUpdated(arg) { super.onUpdated(arg); ElementDrivesTextAnnotation_1.ElementDrivesTextAnnotation.updateFieldDependencies(arg.id, arg.iModel); } collectReferenceIds(referenceIds) { super.collectReferenceIds(referenceIds); collectReferenceIds(this, referenceIds); } /** @internal */ static async onCloned(context, srcProps, dstProps) { await super.onCloned(context, srcProps, dstProps); const srcElem = TextAnnotation2d.fromJSON(srcProps, context.sourceDb); ElementDrivesTextAnnotation_1.ElementDrivesTextAnnotation.remapFields(srcElem, context); const anno = srcElem.getAnnotation(); dstProps.textAnnotationData = anno ? JSON.stringify({ version: exports.TEXT_ANNOTATION_JSON_VERSION, data: anno.toJSON() }) : undefined; return remapTextStyle(context, srcElem, dstProps); } } exports.TextAnnotation2d = TextAnnotation2d; /** An element that displays textual content within a 3d model. * The text is stored as a [TextAnnotation]($common) from which the element's [geometry]($docs/learning/common/GeometryStream.md) and [Placement]($common) are computed. * @see [[setAnnotation]] to change the textual content. * @public @preview */ class TextAnnotation3d extends Element_1.GraphicalElement3d /* implements ITextAnnotation */ { /** @internal */ static get className() { return "TextAnnotation3d"; } /** * The default [[AnnotationTextStyle]] used by the TextAnnotation3d. * @beta */ defaultTextStyle; /** The data associated with the text annotation. */ _textAnnotationProps; /** Extract the textual content, if present. * @see [[setAnnotation]] to change it. */ getAnnotation() { return this._textAnnotationProps ? core_common_1.TextAnnotation.fromJSON(this._textAnnotationProps) : undefined; } /** Change the textual content of the `TextAnnotation3d`. * @see [[getAnnotation]] to extract the current annotation. * @param annotation The new annotation */ setAnnotation(annotation) { this._textAnnotationProps = annotation.toJSON(); } constructor(props, iModel) { super(props, iModel); if (props.defaultTextStyle) { this.defaultTextStyle = new ElementDrivesTextAnnotation_1.TextAnnotationUsesTextStyleByDefault(props.defaultTextStyle.id); } this._textAnnotationProps = parseTextAnnotationData(props.textAnnotationData)?.data; } /** Creates a new instance of `TextAnnotation3d` from its JSON representation. */ static fromJSON(props, iModel) { return new TextAnnotation3d(props, iModel); } /** * Converts the current `TextAnnotation3d` instance to its JSON representation. * It also computes the `elementGeometryBuilderParams` property used to create the GeometryStream. * @inheritdoc */ toJSON() { const props = super.toJSON(); props.textAnnotationData = this._textAnnotationProps ? JSON.stringify({ version: exports.TEXT_ANNOTATION_JSON_VERSION, data: this._textAnnotationProps }) : undefined; if (this._textAnnotationProps) { props.elementGeometryBuilderParams = getElementGeometryBuilderParams(this.iModel, this.model, this.category, this.placement, this._textAnnotationProps, this.defaultTextStyle ? this.defaultTextStyle.id : undefined); } return props; } /** Creates a new `TextAnnotation3d` instance with the specified properties. * @param iModelDb The iModel. * @param arg The arguments for creating the TextAnnotation3d. * @beta */ static create(iModelDb, arg) { const elementProps = { classFullName: this.classFullName, textAnnotationData: arg.textAnnotationProps ? JSON.stringify({ version: exports.TEXT_ANNOTATION_JSON_VERSION, data: arg.textAnnotationProps }) : undefined, defaultTextStyle: arg.defaultTextStyleId ? new ElementDrivesTextAnnotation_1.TextAnnotationUsesTextStyleByDefault(arg.defaultTextStyleId).toJSON() : undefined, placement: arg.placement, model: arg.model, category: arg.category, code: arg.code ?? core_common_1.Code.createEmpty(), }; return new this(elementProps, iModelDb); } /** * Updates the geometry of the TextAnnotation3d on insert and validates version.. * @inheritdoc * @beta */ static onInsert(arg) { super.onInsert(arg); this.validateVersionAndUpdateGeometry(arg); } /** * Updates the geometry of the TextAnnotation3d on update and validates version.. * @inheritdoc * @beta */ static onUpdate(arg) { super.onUpdate(arg); this.validateVersionAndUpdateGeometry(arg); } /** * Populates the `elementGeometryBuilderParams` property in the [TextAnnotation3dProps]($common). * Only does this if the `elementGeometryBuilderParams` is not already set and if there is actually a text annotation to produce geometry for. * Also, validates the version of the text annotation data and migrates it if necessary. * @beta */ static validateVersionAndUpdateGeometry(arg) { const props = arg.props; const textAnnotationData = parseTextAnnotationData(props.textAnnotationData); if (!props.elementGeometryBuilderParams && textAnnotationData) { props.elementGeometryBuilderParams = getElementGeometryBuilderParams(arg.iModel, props.model, props.category, props.placement ?? core_common_1.Placement3d.fromJSON(), textAnnotationData.data, props.defaultTextStyle?.id); } } /** * TextAnnotation3d custom HandledProps include 'textAnnotationData'. * @inheritdoc * @internal */ static _customHandledProps = [ { propertyName: "textAnnotationData", source: "Class" }, ]; /** * TextAnnotation3d deserializes 'textAnnotationData'. * @inheritdoc * @beta */ static deserialize(props) { const elProps = super.deserialize(props); const textAnnotationData = parseTextAnnotationData(props.row.textAnnotationData); if (textAnnotationData) { elProps.textAnnotationData = JSON.stringify(textAnnotationData); } return elProps; } /** * TextAnnotation3d serializes 'textAnnotationData'. * @inheritdoc * @beta */ static serialize(props, iModel) { const inst = super.serialize(props, iModel); if (props.textAnnotationData !== undefined) { inst.textAnnotationData = props.textAnnotationData; } return inst; } /** @internal */ getTextBlocks() { return getTextBlocks(this); } /** @internal */ updateTextBlocks(textBlocks) { return updateTextBlocks(this, textBlocks); } /** @internal */ static onInserted(arg) { super.onInserted(arg); ElementDrivesTextAnnotation_1.ElementDrivesTextAnnotation.updateFieldDependencies(arg.id, arg.iModel); } /** @internal */ static onUpdated(arg) { super.onUpdated(arg); ElementDrivesTextAnnotation_1.ElementDrivesTextAnnotation.updateFieldDependencies(arg.id, arg.iModel); } collectReferenceIds(referenceIds) { super.collectReferenceIds(referenceIds); collectReferenceIds(this, referenceIds); } /** @internal */ static async onCloned(context, srcProps, dstProps) { await super.onCloned(context, srcProps, dstProps); const srcElem = TextAnnotation3d.fromJSON(srcProps, context.sourceDb); ElementDrivesTextAnnotation_1.ElementDrivesTextAnnotation.remapFields(srcElem, context); const anno = srcElem.getAnnotation(); dstProps.textAnnotationData = anno ? JSON.stringify({ version: exports.TEXT_ANNOTATION_JSON_VERSION, data: anno.toJSON() }) : undefined; return remapTextStyle(context, srcElem, dstProps); } } exports.TextAnnotation3d = TextAnnotation3d; async function remapTextStyle(context, srcElem, dstProps) { const dstStyleId = await AnnotationTextStyle.remapTextStyleId(srcElem.defaultTextStyle?.id ?? core_bentley_1.Id64.invalid, context); dstProps.defaultTextStyle = core_bentley_1.Id64.isValid(dstStyleId) ? new ElementDrivesTextAnnotation_1.TextAnnotationUsesTextStyleByDefault(dstStyleId).toJSON() : undefined; } function collectReferenceIds(elem, referenceIds) { const style = elem.defaultTextStyle?.id; if (style && core_bentley_1.Id64.isValidId64(style)) { referenceIds.addElement(style); } const block = elem.getAnnotation()?.textBlock; if (block) { for (const { child } of (0, core_common_1.traverseTextBlockComponent)(block)) { if (child.type === "field") { const hostId = child.propertyHost.elementId; if (core_bentley_1.Id64.isValidId64(hostId)) { referenceIds.addElement(hostId); } } } } } function getTextBlocks(elem) { const annotation = elem.getAnnotation(); return annotation ? [{ textBlock: annotation.textBlock, id: undefined }] : []; } function updateTextBlocks(elem, textBlocks) { (0, core_bentley_1.assert)(textBlocks.length === 1); (0, core_bentley_1.assert)(textBlocks[0].id === undefined); const annotation = elem.getAnnotation(); if (!annotation) { // We must obtain the TextBlockAndId from the element in the first place, so the only way we could end up here is if // somebody removed the text annotation after we called getTextBlocks. That's gotta be a mistake. throw new Error("Text annotation element has no text"); } annotation.textBlock = textBlocks[0].textBlock; elem.setAnnotation(annotation); } /** The version of the JSON stored in `AnnotationTextStyleProps.settings` used by the code. * Uses the same semantics as [ECVersion]($ecschema-metadata). * @internal */ // 1.0.1 - Added terminatorShapes for leaders exports.TEXT_STYLE_SETTINGS_JSON_VERSION = "1.0.1"; function migrateTextStyleSettings(oldData) { if (oldData.version === exports.TEXT_STYLE_SETTINGS_JSON_VERSION) return oldData.data; // Migrate from 1.0.0 to 1.0.1 if (oldData.data.leader && !oldData.data.leader.terminatorShape) { oldData.data.leader.terminatorShape = core_common_1.TextStyleSettings.defaultProps.leader.terminatorShape; } return oldData.data; } /** * The definition element that holds text style information. * The style is stored as a [TextStyleSettings]($common). * @beta */ class AnnotationTextStyle extends Element_1.DefinitionElement { /** @internal */ static get className() { return "AnnotationTextStyle"; } /** * Optional text describing the `AnnotationTextStyle`. */ description; /** * The text style settings for the `AnnotationTextStyle`. * @see [[TextStyleSettings]] for more information. */ settings; constructor(props, iModel) { super(props, iModel); this.description = props.description; const settingsProps = AnnotationTextStyle.parseTextStyleSettings(props.settings); this.settings = core_common_1.TextStyleSettings.fromJSON(settingsProps?.data); } /** * Creates a Code for an `AnnotationTextStyle` given a name that is meant to be unique within the scope of the specified DefinitionModel. * * @param iModel - The IModelDb. * @param definitionModelId - The ID of the DefinitionModel that contains the AnnotationTextStyle and provides the scope for its name. * @param name - The AnnotationTextStyle name. * @beta */ static createCode(iModel, definitionModelId, name) { const codeSpec = iModel.codeSpecs.getByName(core_common_1.BisCodeSpec.annotationTextStyle); return new core_common_1.Code({ spec: codeSpec.id, scope: definitionModelId, value: name }); } /** * Creates a new instance of `AnnotationTextStyle` with the specified properties. * * @param iModelDb - The iModelDb. * @param arg - The arguments for creating the AnnotationTextStyle. * @beta */ static create(iModelDb, arg) { const props = { classFullName: this.classFullName, model: arg.definitionModelId, code: this.createCode(iModelDb, arg.definitionModelId, arg.name).toJSON(), description: arg.description, settings: arg.settings ? JSON.stringify({ version: exports.TEXT_STYLE_SETTINGS_JSON_VERSION, data: arg.settings }) : undefined, }; return new this(props, iModelDb); } /** * Converts the current `AnnotationTextStyle` instance to its JSON representation. * @inheritdoc */ toJSON() { const props = super.toJSON(); props.description = this.description; props.settings = JSON.stringify({ version: exports.TEXT_STYLE_SETTINGS_JSON_VERSION, data: this.settings.toJSON() }); return props; } /** Creates a new instance of `AnnotationTextStyle` from its JSON representation. */ static fromJSON(props, iModel) { return new AnnotationTextStyle(props, iModel); } /** * Validates that the AnnotationTextStyle's settings are valid before insert. * @inheritdoc * @beta */ static onInsert(arg) { super.onInsert(arg); this.validateSettings(arg.props); } /** * Validates that the AnnotationTextStyle's settings are valid before update. * @inheritdoc * @beta */ static onUpdate(arg) { super.onUpdate(arg); this.validateSettings(arg.props); } static validateSettings(props) { const settingProps = AnnotationTextStyle.parseTextStyleSettings(props.settings); if (!settingProps) return; const settings = core_common_1.TextStyleSettings.fromJSON(settingProps.data); const errors = settings.getValidationErrors(); if (errors.length > 0) { throw new Error(`Invalid AnnotationTextStyle settings: ${errors.join(", ")}`); } } /** * AnnotationTextStyle custom HandledProps include 'settings'. * @inheritdoc * @beta */ static _customHandledProps = [ { propertyName: "settings", source: "Class" }, ]; /** * AnnotationTextStyle deserializes 'settings'. * @inheritdoc * @beta */ static deserialize(props) { const elProps = super.deserialize(props); const settings = this.parseTextStyleSettings(props.row.settings); if (settings) { elProps.settings = JSON.stringify(settings); } return elProps; } /** * AnnotationTextStyle serializes 'settings'. * @inheritdoc * @beta */ static serialize(props, iModel) { const inst = super.serialize(props, iModel); if (props.settings !== undefined) { inst.settings = props.settings; } return inst; } /** Parses, validates, and potentially migrates the text style settings data from a JSON string. */ static parseTextStyleSettings(json) { if (!json) return undefined; return validateAndMigrateVersionedJSON(json, exports.TEXT_STYLE_SETTINGS_JSON_VERSION, migrateTextStyleSettings); } /** When copying an element from one iModel to another, returns the Id of the AnnotationTextStyle in the `context`'s target iModel * corresponding to `sourceTextStyleId`, or [Id64.invalid]($bentley) if no corresponding text style exists. * If a text style with the same [Code]($common) exists in the target iModel, the style Id will be remapped to refer to that style. * Otherwise, a copy of the style will be imported into the target iModel and its element Id returned. * Implementations of [[ITextAnnotation]] should invoke this function when implementing their [[Element.onCloned]] method. * @throws Error if an attempt to import the text style failed. */ static async remapTextStyleId(sourceTextStyleId, context) { // No remapping necessary if there's no text style or we're not copying to a different iModel. if (!core_bentley_1.Id64.isValid(sourceTextStyleId) || !context.isBetweenIModels) { return sourceTextStyleId; } // If the style's already been remapped, we're finished. let dstStyleId = context.findTargetElementId(sourceTextStyleId); if (core_bentley_1.Id64.isValid(dstStyleId)) { return dstStyleId; } // Look up the style. It really ought to exist. const srcStyle = context.sourceDb.elements.tryGetElement(sourceTextStyleId); if (!srcStyle) { return core_bentley_1.Id64.invalid; } // If a style with the same code exists in the target iModel, remap to that one. dstStyleId = context.targetDb.elements.queryElementIdByCode(srcStyle.code); if (undefined !== dstStyleId) { return dstStyleId; } // Copy the style into the target iModel and remap its Id. const dstStyleProps = await context.cloneElement(srcStyle); dstStyleId = context.targetDb.elements.insertElement(dstStyleProps); context.remapElement(sourceTextStyleId, dstStyleId); return dstStyleId; } static async onCloned(context, srcProps, dstProps) { await super.onCloned(context, srcProps, dstProps); if (!context.isBetweenIModels) { return; } const settingsProps = AnnotationTextStyle.parseTextStyleSettings(srcProps.settings); const font = core_common_1.TextStyleSettings.fromJSON(settingsProps?.data).font; const fontsToEmbed = []; for (const file of context.sourceDb.fonts.queryEmbeddedFontFiles()) { if (file.type === font.type && file.faces.some((face) => face.familyName === font.name)) { fontsToEmbed.push(file); } } await Promise.all(fontsToEmbed.map(async (file) => context.targetDb.fonts.embedFontFile({ file }))); } } exports.AnnotationTextStyle = AnnotationTextStyle; //# sourceMappingURL=TextAnnotationElement.js.map