@itwin/core-backend
Version:
iTwin.js backend components
183 lines • 7.81 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module iModels
*/
import { FontType } from "@itwin/core-common";
import { _faceProps, _getData, _implementationProhibited, _key, _nativeDb } from "./Symbols";
import { EmbeddedFontFile } from "./FontFileImpl";
import { assert, DbResult, Logger } from "@itwin/core-bentley";
import { BackendLoggerCategory } from "../BackendLoggerCategory";
class IModelDbFontsImpl {
[_implementationProhibited] = undefined;
#db;
constructor(iModel) {
this.#db = iModel;
}
queryMappedFamilies(args) {
const fontProps = this.#queryFontTable();
if (args?.includeNonEmbedded) {
return fontProps;
}
const fontNames = this.#getEmbeddedFontNames();
return fontProps.filter((x) => fontNames.includes(x.name));
}
queryEmbeddedFontFiles() {
const files = [];
this.#db.withSqliteStatement(`SELECT Id,StrData FROM be_Prop WHERE Namespace="dgn_Font" AND Name="EmbeddedFaceData"`, (stmt) => {
while (DbResult.BE_SQLITE_ROW === stmt.step()) {
let faces;
try {
faces = JSON.parse(stmt.getValueString(1));
}
catch (e) {
Logger.logException(BackendLoggerCategory.IModelDb, e);
}
if (!Array.isArray(faces) || faces.length === 0) {
continue;
}
let type = faces[0].type;
if (type !== FontType.Rsc && type !== FontType.Shx) {
type = FontType.TrueType;
}
files.push(new EmbeddedFontFile(this.#db, stmt.getValueInteger(0), type, faces));
}
});
return files;
}
async embedFontFile(args) {
this.#requireWritable();
if (!args.file.isEmbeddable) {
throw new Error("Font does not permit embedding");
}
const file = args.file;
for (const existing of this.queryEmbeddedFontFiles()) {
if (existing[_key] === file[_key]) {
// Already embedded - it's a no-op.
return;
}
}
let id = 0;
const codes = this.#db.codeService?.internalCodes;
if (codes) {
id = await codes.writeLocker.reserveEmbeddedFaceDataId(args.file[_key]);
}
else {
// CodeService not configured - schema lock required to prevent conflicting Ids in be_Prop table.
await this.#db.acquireSchemaLock();
const sql = `SELECT MAX(Id) FROM be_Prop WHERE Namespace="dgn_Font" AND Name="EmbeddedFaceData"`;
id = this.#db.withSqliteStatement(sql, (stmt) => stmt.nextRow() ? stmt.getValueInteger(0) + 1 : 1);
}
assert(id > 0);
const data = file[_getData]();
this.#db[_nativeDb].embedFontFile(id, file[_faceProps], data, true);
if (!args.skipFontIdAllocation) {
const familyNames = new Set(args.file.faces.map((x) => x.familyName));
const acquireIds = Array.from(familyNames).map(async (x) => this.#acquireId({ name: x, type: args.file.type }, true).catch());
await Promise.allSettled(acquireIds);
}
this.#db.clearFontMap();
}
findId(selector) {
let id;
const sqlPostlude = undefined === selector.type ? " ORDER BY Type ASC" : " AND Type=?";
const sql = `SELECT Id FROM dgn_Font WHERE Name=?${sqlPostlude}`;
this.#db.withPreparedSqliteStatement(sql, (stmt) => {
stmt.bindString(1, selector.name);
if (undefined !== selector.type) {
stmt.bindInteger(2, selector.type);
}
if (DbResult.BE_SQLITE_ROW === stmt.step()) {
id = stmt.getValueInteger(0);
}
});
return id;
}
findDescriptor(id) {
let name, type;
this.#db.withPreparedSqliteStatement("SELECT Name,Type FROM dgn_Font WHERE Id=?", (stmt) => {
stmt.bindInteger(1, id);
if (DbResult.BE_SQLITE_ROW === stmt.step()) {
const stmtName = stmt.getValueString(0);
if (stmtName.length > 0) {
name = stmtName;
const typeCode = stmt.getValueInteger(1);
type = (typeCode === FontType.Shx || typeCode === FontType.Rsc) ? typeCode : FontType.TrueType;
}
}
});
return undefined !== name && undefined !== type ? { name, type } : undefined;
}
async acquireId(descriptor) {
this.#requireWritable();
return this.#acquireId(descriptor, false);
}
async #acquireId(descriptor, embeddingFaceData) {
let id = this.findId(descriptor);
if (undefined !== id) {
return id;
}
const codes = this.#db.codeService?.internalCodes;
if (codes) {
id = await codes.writeLocker.reserveFontId({ fontName: descriptor.name, fontType: descriptor.type });
}
else {
// If we're being called from `embedFontFile` then the schema lock is already held, don't bother re-acquiring it.
if (!embeddingFaceData) {
// No CodeService configured. We must obtain the schema lock and use the next available Id.
await this.#db.acquireSchemaLock();
}
id = this.#db.withSqliteStatement(`SELECT MAX(Id) FROM dgn_Font`, (stmt) => stmt.nextRow() ? stmt.getValueInteger(0) + 1 : 1);
}
this.#db.withSqliteStatement(`INSERT INTO dgn_Font (Id,Type,Name) VALUES (?,?,?)`, (stmt) => {
stmt.bindInteger(1, id);
stmt.bindInteger(2, descriptor.type);
stmt.bindString(3, descriptor.name);
if (DbResult.BE_SQLITE_DONE !== stmt.step()) {
throw new Error("Failed to insert font Id mapping");
}
});
this.#db.clearFontMap();
return id;
}
#requireWritable() {
if (this.#db.isReadonly) {
throw new Error("iModel is read-only");
}
}
#getEmbeddedFontNames() {
const names = [];
const sql = `select DISTINCT json_extract(face.value, '$.familyName') from be_Prop, json_each(be_Prop.StrData) as face where namespace="dgn_Font" and name="EmbeddedFaceData"`;
this.#db.withPreparedSqliteStatement(sql, (stmt) => {
while (DbResult.BE_SQLITE_ROW === stmt.step()) {
names.push(stmt.getValueString(0));
}
});
return names;
}
#queryFontTable() {
const fonts = [];
const sql = `SELECT Id,Name,Type FROM dgn_Font`;
this.#db.withPreparedSqliteStatement(sql, (stmt) => {
while (DbResult.BE_SQLITE_ROW === stmt.step()) {
const name = stmt.getValueString(1);
const typeCode = stmt.getValueInteger(2);
const type = (typeCode === FontType.Shx || typeCode === FontType.Rsc) ? typeCode : FontType.TrueType;
if (name.length > 0) {
fonts.push({
name,
type,
id: stmt.getValueInteger(0),
});
}
}
});
return fonts;
}
}
export function createIModelDbFonts(db) {
return new IModelDbFontsImpl(db);
}
//# sourceMappingURL=IModelDbFontsImpl.js.map