From 2956b718c20c6e7b7236d3e7f5c357e10cdb26b9 Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Wed, 18 Feb 2026 01:05:42 +0000 Subject: [PATCH 01/13] feat: implement lazy loading for external files --- packages/ducjs/src/index.ts | 9 +- packages/ducjs/src/lazy-files.ts | 238 ++++++++++++++++++++++++++++++ packages/ducjs/src/parse.ts | 244 ++++++++++++++++++++++++++++++- 3 files changed, 485 insertions(+), 6 deletions(-) create mode 100644 packages/ducjs/src/lazy-files.ts diff --git a/packages/ducjs/src/index.ts b/packages/ducjs/src/index.ts index a256895..e29cd55 100644 --- a/packages/ducjs/src/index.ts +++ b/packages/ducjs/src/index.ts @@ -1,8 +1,9 @@ export * as DucBin from "./flatbuffers/duc"; -export * from "./types"; -export * from "./utils"; -export * from "./serialize"; +export * from "./lazy-files"; export * from "./parse"; export * from "./restore"; -export * from "./technical"; \ No newline at end of file +export * from "./serialize"; +export * from "./technical"; +export * from "./types"; +export * from "./utils"; diff --git a/packages/ducjs/src/lazy-files.ts b/packages/ducjs/src/lazy-files.ts new file mode 100644 index 0000000..f476fac --- /dev/null +++ b/packages/ducjs/src/lazy-files.ts @@ -0,0 +1,238 @@ +/** + * LazyExternalFileStore — Zero-copy, on-demand access to external file data from a FlatBuffer. + * + * Instead of eagerly parsing and copying every external file's binary data into JS memory, + * this store keeps a reference to the original FlatBuffer Uint8Array and reads file bytes + * only when explicitly requested. FlatBuffer `dataArray()` returns a zero-copy view + * (a Uint8Array pointing into the original buffer), so no allocation occurs until the + * consumer actually needs the data. + * + * Memory lifecycle: + * 1. On parse: only metadata (~200 bytes per file) enters JS heap. + * 2. On demand: `getFileData(fileId)` reads the zero-copy slice from the buffer. + * 3. The caller (renderer/worker) uses the data, then lets it GC naturally. + * 4. If the store is released, the buffer reference is dropped, freeing everything. + * + * This is the key to supporting 1000s of external files without RAM bloat. + */ + +import * as flatbuffers from "flatbuffers"; +import { ExportedDataState as ExportedDataStateFb } from "./flatbuffers/duc"; +import type { DucExternalFileData, DucExternalFileMetadata, DucExternalFiles } from "./types"; +import type { ExternalFileId } from "./types/elements"; + +export type ExternalFileMetadataMap = Record; + +interface LazyFileEntry { + metadata: DucExternalFileMetadata; + /** Index into the ExportedDataState.externalFiles vector */ + vectorIndex: number; +} + +export class LazyExternalFileStore { + private _buffer: Uint8Array | null; + private _byteBuffer: flatbuffers.ByteBuffer | null; + private _dataState: ExportedDataStateFb | null; + + /** Map from file id → lazy entry */ + private _entries = new Map(); + /** Map from element key → file id (the external_files vector uses element id as key) */ + private _keyToFileId = new Map(); + + /** + * Files that were added at runtime (e.g. user uploading a new image). + * These aren't in the original FlatBuffer so we hold their data directly. + */ + private _runtimeFiles = new Map(); + + constructor(buffer: Uint8Array) { + this._buffer = buffer; + this._byteBuffer = new flatbuffers.ByteBuffer(buffer); + this._dataState = ExportedDataStateFb.getRootAsExportedDataState(this._byteBuffer); + this._indexMetadata(); + console.info(`[LazyExternalFileStore] indexed ${this._entries.size} files from ${buffer.byteLength} byte buffer, ids: [${[...this._entries.keys()].map(k => k.slice(0, 12)).join(', ')}]`); + } + + private _indexMetadata(): void { + if (!this._dataState) return; + + const count = this._dataState.externalFilesLength(); + for (let i = 0; i < count; i++) { + const entry = this._dataState.externalFiles(i); + if (!entry) continue; + + const key = entry.key(); + const fileData = entry.value(); + if (!key || !fileData) continue; + + const id = fileData.id() as ExternalFileId | null; + if (!id) continue; + + const metadata: DucExternalFileMetadata = { + id, + mimeType: fileData.mimeType() || "application/octet-stream", + created: Number(fileData.created()), + lastRetrieved: Number(fileData.lastRetrieved()) || undefined, + }; + + const lazyEntry: LazyFileEntry = { metadata, vectorIndex: i }; + this._entries.set(id, lazyEntry); + this._keyToFileId.set(key, id); + } + } + + /** Total number of external files */ + get size(): number { + return this._entries.size + this._runtimeFiles.size; + } + + /** Whether a file with the given id exists */ + has(fileId: string): boolean { + return this._entries.has(fileId) || this._runtimeFiles.has(fileId); + } + + /** Get metadata only (no binary data copied) — ~200 bytes per file */ + getMetadata(fileId: string): DucExternalFileMetadata | null { + const runtime = this._runtimeFiles.get(fileId); + if (runtime) { + const { data: _, ...meta } = runtime; + return meta; + } + return this._entries.get(fileId)?.metadata ?? null; + } + + /** Get all metadata entries (for UI listing, etc.) */ + getAllMetadata(): ExternalFileMetadataMap { + const result: ExternalFileMetadataMap = {}; + + for (const [id, entry] of this._entries) { + result[id] = entry.metadata; + } + for (const [id, file] of this._runtimeFiles) { + const { data: _, ...meta } = file; + result[id] = meta; + } + + return result; + } + + /** + * Get full file data (metadata + binary bytes) ON DEMAND. + * + * For files from the original FlatBuffer, this returns a zero-copy Uint8Array + * view into the original buffer — no allocation for the file bytes themselves. + * The view is valid as long as this store hasn't been released. + * + * For runtime-added files, returns the data directly. + */ + getFileData(fileId: string): DucExternalFileData | null { + const runtime = this._runtimeFiles.get(fileId); + if (runtime) return runtime; + + const entry = this._entries.get(fileId); + if (!entry || !this._dataState) return null; + + const fbEntry = this._dataState.externalFiles(entry.vectorIndex); + if (!fbEntry) return null; + + const fileData = fbEntry.value(); + if (!fileData) return null; + + const data = fileData.dataArray(); + if (!data) return null; + + return { + ...entry.metadata, + data, + }; + } + + /** + * Get a detached copy of the file data (allocates new ArrayBuffer). + * Use this when you need to transfer data to a worker or keep it beyond store lifetime. + */ + getFileDataCopy(fileId: string): DucExternalFileData | null { + const fileDataRef = this.getFileData(fileId); + if (!fileDataRef) return null; + + return { + ...fileDataRef, + data: new Uint8Array(fileDataRef.data), + }; + } + + /** + * Add a file at runtime (user upload, paste, etc.). + * These files are held in memory since they aren't in the FlatBuffer. + */ + addRuntimeFile(fileData: DucExternalFileData): void { + this._runtimeFiles.set(fileData.id, fileData); + } + + /** Remove a runtime-added file */ + removeRuntimeFile(fileId: string): void { + this._runtimeFiles.delete(fileId); + } + + /** + * Export all files as a standard DucExternalFiles record. + * This COPIES all file data eagerly — use only for serialization. + */ + toExternalFiles(): DucExternalFiles { + const result: DucExternalFiles = {}; + + for (const [key, fileId] of this._keyToFileId) { + const fileData = this.getFileData(fileId); + if (fileData) { + result[key] = fileData; + } + } + + for (const [id, file] of this._runtimeFiles) { + result[id] = file; + } + + return result; + } + + /** + * Merge runtime files from the given DucExternalFiles map. + * Only adds files not already present in the store. + */ + mergeFiles(files: DucExternalFiles): void { + for (const [_key, fileData] of Object.entries(files)) { + if (!this.has(fileData.id)) { + this.addRuntimeFile(fileData); + } + } + } + + /** Estimated RAM usage for metadata only (not counting the backing buffer) */ + get estimatedMetadataBytes(): number { + let bytes = 0; + for (const [, entry] of this._entries) { + bytes += 200 + entry.metadata.id.length * 2 + entry.metadata.mimeType.length * 2; + } + for (const [, file] of this._runtimeFiles) { + bytes += 200 + (file.data?.byteLength ?? 0); + } + return bytes; + } + + /** + * Release the FlatBuffer reference. After this, only runtime-added files remain accessible. + * Call this when switching documents or when the store is no longer needed. + */ + release(): void { + this._buffer = null; + this._byteBuffer = null; + this._dataState = null; + this._entries.clear(); + this._keyToFileId.clear(); + } + + /** Whether the store has been released */ + get isReleased(): boolean { + return this._buffer === null; + } +} diff --git a/packages/ducjs/src/parse.ts b/packages/ducjs/src/parse.ts index 33c30bd..653cd2d 100644 --- a/packages/ducjs/src/parse.ts +++ b/packages/ducjs/src/parse.ts @@ -4,8 +4,9 @@ import * as flatbuffers from "flatbuffers"; import { nanoid } from 'nanoid'; import { CustomHatchPattern as CustomHatchPatternFb, - DimensionToleranceStyle as DimensionToleranceStyleFb, DOCUMENT_GRID_ALIGN_ITEMS, + DimensionToleranceStyle as DimensionToleranceStyleFb, + DocumentGridConfig as DocumentGridConfigFb, DucArrowElement as DucArrowElementFb, DucBlockCollection as DucBlockCollectionFb, DucBlock as DucBlockFb, @@ -61,7 +62,6 @@ import { DucViewportStyle as DucViewportStyleFb, DucXRayElement as DucXRayElementFb, DucXRayStyle as DucXRayStyleFb, - DocumentGridConfig as DocumentGridConfigFb, ElementBackground as ElementBackgroundFb, ElementContentBase as ElementContentBaseFb, ElementStroke as ElementStrokeFb, @@ -118,6 +118,7 @@ import { DucElement, DucEllipseElement, DucEmbeddableElement, + DucExternalFileMetadata, DucExternalFiles, DucFeatureControlFrameElement, DucFeatureControlFrameStyle, @@ -1395,6 +1396,32 @@ export function parseExternalFilesFromBinary(entry: DucExternalFileEntry): DucEx } as DucExternalFiles; } +/** + * Parse only metadata (no binary data) from an external file entry. + * Used by the lazy file store to avoid copying file bytes into JS memory. + */ +export function parseExternalFileMetadataFromBinary(entry: DucExternalFileEntry): { + key: string; + metadata: DucExternalFileMetadata; +} | null { + const fileData = entry.value(); + const key = entry.key(); + if (!fileData || !key) return null; + + const id = fileData.id() as ExternalFileId | null; + if (!id) return null; + + return { + key, + metadata: { + id, + mimeType: fileData.mimeType() || "application/octet-stream", + created: Number(fileData.created()), + lastRetrieved: Number(fileData.lastRetrieved()) || undefined, + }, + }; +} + export function parseGlobalStateFromBinary(state: DucGlobalStateFb): DucGlobalState { return { name: state.name(), @@ -2010,3 +2037,216 @@ export const parseDuc = async ( }; }; // #endregion + +// #region LAZY ROOT PARSER + +import { LazyExternalFileStore } from "./lazy-files"; + +export type LazyRestoredDataState = Omit & { + /** Lazy file store: only metadata is in memory, file bytes are read on-demand from the buffer */ + lazyFileStore: LazyExternalFileStore; + /** Legacy `files` field — always empty. Use lazyFileStore instead. */ + files: DucExternalFiles; +}; + +/** + * Parse a .duc binary with lazy external file loading. + * + * This is identical to `parseDuc` except: + * - External file BYTES are NOT copied into JS memory. + * - Only file metadata (~200 bytes per file) is parsed. + * - A `LazyExternalFileStore` is returned for on-demand data access. + * - The store holds a reference to the original Uint8Array buffer. + * + * Memory comparison for 500 PDFs averaging 5MB each: + * - parseDuc: files field holds 2.5GB of ArrayBuffer data in JS heap + * - parseDucLazy: ~100KB of metadata + the original buffer (referenced, not copied) + * + * @param buffer - The raw .duc file bytes (from storage/IndexedDB/filesystem) + * @param restoreConfig - Optional restore configuration + */ +export const parseDucLazy = async ( + buffer: Uint8Array, + restoreConfig: RestoreConfig = {}, +): Promise => { + if (!buffer || buffer.byteLength === 0) { + throw new Error('Invalid DUC buffer: empty file'); + } + + const byteBuffer = new flatbuffers.ByteBuffer(buffer); + + let data: ExportedDataStateFb; + try { + data = ExportedDataState.getRootAsExportedDataState(byteBuffer); + } catch (e) { + throw new Error('Invalid DUC buffer: cannot read root table'); + } + + const legacyVersion = data.versionLegacy(); + if (legacyVersion) { + throw new Error(`Unsupported DUC version: ${legacyVersion}. Please use version ducjs@2.0.1 or lower to support this file.`); + } + + const localState = data.ducLocalState(); + const parsedLocalState = localState && parseLocalStateFromBinary(localState); + + const globalState = data.ducGlobalState(); + const parsedGlobalState = globalState && parseGlobalStateFromBinary(globalState); + + // Parse elements + const elements: Partial[] = []; + for (let i = 0; i < data.elementsLength(); i++) { + const e = data.elements(i); + if (e) { + const element = parseElementFromBinary(e); + if (element) { + elements.push(element); + } + } + } + + // Create lazy file store — only metadata is parsed, no file bytes copied + const lazyFileStore = new LazyExternalFileStore(buffer); + + // Parse blocks + const blocks: DucBlock[] = []; + for (let i = 0; i < data.blocksLength(); i++) { + const block = data.blocks(i); + if (block) { + const parsedBlock = parseBlockFromBinary(block); + if (parsedBlock) { + blocks.push(parsedBlock as DucBlock); + } + } + } + + // Parse block instances + const blockInstances: DucBlockInstance[] = []; + for (let i = 0; i < data.blockInstancesLength(); i++) { + const blockInstance = data.blockInstances(i); + if (blockInstance) { + const parsedBlockInstance = parseBlockInstance(blockInstance); + if (parsedBlockInstance) { + blockInstances.push(parsedBlockInstance); + } + } + } + + // Parse block collections + const blockCollections: DucBlockCollection[] = []; + for (let i = 0; i < data.blockCollectionsLength(); i++) { + const blockCollection = data.blockCollections(i); + if (blockCollection) { + const parsedBlockCollection = parseBlockCollection(blockCollection); + if (parsedBlockCollection) { + blockCollections.push(parsedBlockCollection); + } + } + } + + // Parse groups + const groups: DucGroup[] = []; + for (let i = 0; i < data.groupsLength(); i++) { + const group = data.groups(i); + if (group) { + const parsedGroup = parseGroupFromBinary(group); + if (parsedGroup) { + groups.push(parsedGroup as DucGroup); + } + } + } + + // Parse dictionary + const dictionary = parseDictionaryFromBinary(data); + + // Parse thumbnail + const thumbnail = parseThumbnailFromBinary(data); + + // Parse version graph + const versionGraphData = data.versionGraph(); + const versionGraph = parseVersionGraphFromBinary(versionGraphData); + + // Parse regions + const regions: DucRegion[] = []; + for (let i = 0; i < data.regionsLength(); i++) { + const region = data.regions(i); + if (region) { + const parsedRegion = parseRegionFromBinary(region); + if (parsedRegion) { + regions.push(parsedRegion); + } + } + } + + // Parse layers + const layers: DucLayer[] = []; + for (let i = 0; i < data.layersLength(); i++) { + const layer = data.layers(i); + if (layer) { + const parsedLayer = parseLayerFromBinary(layer); + if (parsedLayer) { + layers.push(parsedLayer); + } + } + } + + // Parse standards + const standards: Standard[] = []; + for (let i = 0; i < data.standardsLength(); i++) { + const standard = data.standards(i); + if (standard) { + const parsedStandard = parseStandardFromBinary(standard); + if (parsedStandard) { + standards.push(parsedStandard); + } + } + } + + const exportData: RestoredDataState = { + thumbnail, + dictionary, + elements: elements as OrderedDucElement[], + localState: parsedLocalState!, + globalState: parsedGlobalState!, + blocks, + blockInstances, + blockCollections, + groups, + regions, + layers, + standards, + files: {}, // empty — use lazyFileStore + versionGraph: versionGraph ?? undefined, + id: data.id() ?? nanoid(), + }; + + const sanitized = restore( + exportData, + { + syncInvalidIndices: (elements) => elements as OrderedDucElement[], + repairBindings: true, + refreshDimensions: false, + }, + restoreConfig, + ); + + return { + thumbnail: sanitized.thumbnail, + dictionary: sanitized.dictionary, + elements: sanitized.elements, + localState: sanitized.localState, + globalState: sanitized.globalState, + files: {}, + lazyFileStore, + blocks: sanitized.blocks, + blockInstances: sanitized.blockInstances, + groups: sanitized.groups, + regions: sanitized.regions, + layers: sanitized.layers, + blockCollections: sanitized.blockCollections, + standards: sanitized.standards, + versionGraph: sanitized.versionGraph, + id: sanitized.id, + }; +}; +// #endregion From 99b88bd1ea0e0fc1dff57806cd894c0200ae14f9 Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Fri, 20 Feb 2026 22:58:26 +0000 Subject: [PATCH 02/13] refactor: update font handling and legacy support --- packages/ducjs/src/types/elements/index.ts | 5 +- packages/ducjs/src/utils/constants.ts | 46 ++++++++++++------- .../ducjs/src/utils/elements/newElement.ts | 2 +- .../ducjs/src/utils/elements/textElement.ts | 28 ++++++----- 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/packages/ducjs/src/types/elements/index.ts b/packages/ducjs/src/types/elements/index.ts index 177f8e7..89a8673 100644 --- a/packages/ducjs/src/types/elements/index.ts +++ b/packages/ducjs/src/types/elements/index.ts @@ -711,7 +711,8 @@ export type InitializedDucImageElement = MarkNonNullable< //// === TEXT ELEMENTS === export type FontFamilyKeys = keyof typeof FONT_FAMILY; export type FontFamilyValues = typeof FONT_FAMILY[FontFamilyKeys]; -export type FontString = string & { _brand: "fontString" }; +/** Font family identifier — any valid CSS font-family string (Google Font name, system font, etc.) */ +export type FontString = string; export type TextAlign = ValueOf; export type VerticalAlign = ValueOf; export type LineSpacingType = ValueOf; @@ -727,7 +728,7 @@ export type DucTextStyle = { /** * The primary font family to use for the text */ - fontFamily: FontFamilyValues; + fontFamily: FontString; /** * Fallback font family for broader compatibility across all systems and languages * Useful for emojis, non-latin characters, etc. diff --git a/packages/ducjs/src/utils/constants.ts b/packages/ducjs/src/utils/constants.ts index 5198c21..31645ad 100644 --- a/packages/ducjs/src/utils/constants.ts +++ b/packages/ducjs/src/utils/constants.ts @@ -127,24 +127,38 @@ export const DEFAULT_PROPORTIONAL_RADIUS = 0.25; export const DEFAULT_ADAPTIVE_RADIUS = 32; /** - * // TODO: shouldn't be really `const`, likely neither have integers as values, due to value for the custom fonts, which should likely be some hash. + * Font family identifiers. Values are the actual CSS font-family names + * so they can be passed directly to Google Fonts / Canvas2D. * - * Let's think this through and consider: - * - https://developer.mozilla.org/en-US/docs/Web/CSS/generic-family - * - https://drafts.csswg.org/css-fonts-4/#font-family-prop - * - https://learn.microsoft.com/en-us/typography/opentype/spec/ibmfc + * For backward compatibility with old files that stored numeric IDs, + * use `LEGACY_FONT_ID_TO_NAME` to resolve them. */ export const FONT_FAMILY = { - Virgil: 1, - Helvetica: 2, - Cascadia: 3, - // leave 4 unused as it was historically used for Assistant (which we don't use anymore) or custom font (Obsidian) - Excalifont: 5, - Nunito: 6, - "Lilita One": 7, - "Comic Shanns": 8, - "Liberation Sans": 9, - "Roboto Mono": 10, + Virgil: "Virgil", + Helvetica: "Helvetica", + Cascadia: "Cascadia", + Excalifont: "Excalifont", + Nunito: "Nunito", + "Lilita One": "Lilita One", + "Comic Shanns": "Comic Shanns", + "Liberation Sans": "Liberation Sans", + "Roboto Mono": "Roboto Mono", +} as const; + +/** + * Reverse mapping from legacy numeric font IDs to font family names. + * Used when loading old .duc files that encoded fontFamily as a number. + */ +export const LEGACY_FONT_ID_TO_NAME: Record = { + 1: "Virgil", + 2: "Helvetica", + 3: "Cascadia", + 5: "Excalifont", + 6: "Nunito", + 7: "Lilita One", + 8: "Comic Shanns", + 9: "Liberation Sans", + 10: "Roboto Mono", }; export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji"; @@ -152,7 +166,7 @@ export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji"; export const DEFAULT_VERSION = "{version}"; export const MIN_FONT_SIZE = 1; export const DEFAULT_FONT_SIZE = 20; -export const DEFAULT_FONT_FAMILY: FontFamilyValues = FONT_FAMILY["Roboto Mono"]; +export const DEFAULT_FONT_FAMILY: FontFamilyValues = FONT_FAMILY["Roboto Mono"] as FontFamilyValues; export const DEFAULT_TEXT_ALIGN: DucTextElement["textAlign"] = TEXT_ALIGN.LEFT; export const DEFAULT_VERTICAL_ALIGN: DucTextElement["verticalAlign"] = VERTICAL_ALIGN.TOP; export const DEFAULT_LINE_HEIGHT = 1; diff --git a/packages/ducjs/src/utils/elements/newElement.ts b/packages/ducjs/src/utils/elements/newElement.ts index 76ea174..f9d9b0f 100644 --- a/packages/ducjs/src/utils/elements/newElement.ts +++ b/packages/ducjs/src/utils/elements/newElement.ts @@ -300,7 +300,7 @@ export const newTextElement = ( } & Partial & ElementConstructorOpts, ): NonDeleted => { const scope = opts.scope ?? currentScope; - const fontFamily = opts.fontFamily || DEFAULT_FONT_FAMILY; + const fontFamily = opts.fontFamily || (DEFAULT_FONT_FAMILY as string); const fontSize = opts.fontSize || getPrecisionValueFromRaw(DEFAULT_FONT_SIZE as RawValue, scope, currentScope); const lineHeight = opts.lineHeight || (1.2 as DucTextElement["lineHeight"]); const text = normalizeText(opts.text); diff --git a/packages/ducjs/src/utils/elements/textElement.ts b/packages/ducjs/src/utils/elements/textElement.ts index 60fbbf7..9d24c26 100644 --- a/packages/ducjs/src/utils/elements/textElement.ts +++ b/packages/ducjs/src/utils/elements/textElement.ts @@ -5,7 +5,7 @@ import { isArrowElement, isBoundToContainer, isTextElement } from "../../types/e import { GeometricPoint } from "../../types/geometryTypes"; import { ExtractSetType } from "../../types/utility-types"; import { getContainerElement, getElementAbsoluteCoords, getResizedElementAbsoluteCoords } from "../bounds"; -import { ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO, ARROW_LABEL_WIDTH_FRACTION, BOUND_TEXT_PADDING, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, FONT_FAMILY, WINDOWS_EMOJI_FALLBACK_FONT } from "../constants"; +import { ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO, ARROW_LABEL_WIDTH_FRACTION, BOUND_TEXT_PADDING, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, FONT_FAMILY, LEGACY_FONT_ID_TO_NAME, WINDOWS_EMOJI_FALLBACK_FONT } from "../constants"; import { getBoundTextElementPosition, getPointGlobalCoordinates, getPointsGlobalCoordinates, getSegmentMidPoint } from "./linearElement"; import { adjustXYWithRotation } from "../math"; import { normalizeText } from "../normalize"; @@ -641,17 +641,23 @@ export const getTextFromElements = ( export const getFontFamilyString = ({ fontFamily, }: { - fontFamily: FontFamilyValues; + fontFamily: FontFamilyValues | string; }) => { - // Handle both number and string fontFamily values - const fontFamilyNum = typeof fontFamily === 'string' ? parseInt(fontFamily, 10) : fontFamily; - - for (const [fontFamilyString, id] of Object.entries(FONT_FAMILY)) { - if (id === fontFamilyNum) { - return `${fontFamilyString}, ${WINDOWS_EMOJI_FALLBACK_FONT}`; - } + // Handle legacy numeric font IDs from old files + if (typeof fontFamily === "number") { + const name = LEGACY_FONT_ID_TO_NAME[fontFamily]; + if (name) return `${name}, ${WINDOWS_EMOJI_FALLBACK_FONT}`; + return WINDOWS_EMOJI_FALLBACK_FONT; + } + + // Handle stringified numeric IDs (e.g. "10") + const parsed = Number(fontFamily); + if (!Number.isNaN(parsed) && LEGACY_FONT_ID_TO_NAME[parsed]) { + return `${LEGACY_FONT_ID_TO_NAME[parsed]}, ${WINDOWS_EMOJI_FALLBACK_FONT}`; } - return WINDOWS_EMOJI_FALLBACK_FONT; + + // New path: fontFamily is already a string name + return `${fontFamily}, ${WINDOWS_EMOJI_FALLBACK_FONT}`; }; /** returns fontSize+fontFamily string for assignment to DOM elements */ @@ -660,7 +666,7 @@ export const getFontString = ({ fontFamily, }: { fontSize: DucTextElement["fontSize"]; - fontFamily: FontFamilyValues; + fontFamily: FontFamilyValues | string; }) => { return `${fontSize.scoped}px ${getFontFamilyString({ fontFamily })}` as FontString; }; From 4d3c4a3fd769a69c60f5e13a5fba8714bf9776b5 Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Fri, 20 Feb 2026 22:58:39 +0000 Subject: [PATCH 03/13] refactor: add DOCUMENT_GRID_ALIGN_ITEMS import --- packages/ducpy/src/ducpy/classes/ElementsClass.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ducpy/src/ducpy/classes/ElementsClass.py b/packages/ducpy/src/ducpy/classes/ElementsClass.py index ad00bc9..8e27350 100644 --- a/packages/ducpy/src/ducpy/classes/ElementsClass.py +++ b/packages/ducpy/src/ducpy/classes/ElementsClass.py @@ -8,6 +8,7 @@ from ducpy.Duc.BOOLEAN_OPERATION import BOOLEAN_OPERATION from ducpy.Duc.COLUMN_TYPE import COLUMN_TYPE from ducpy.Duc.DATUM_BRACKET_STYLE import DATUM_BRACKET_STYLE +from ducpy.Duc.DOCUMENT_GRID_ALIGN_ITEMS import DOCUMENT_GRID_ALIGN_ITEMS from ducpy.Duc.DIMENSION_FIT_RULE import DIMENSION_FIT_RULE from ducpy.Duc.DIMENSION_TEXT_PLACEMENT import DIMENSION_TEXT_PLACEMENT from ducpy.Duc.DIMENSION_TYPE import DIMENSION_TYPE From 0ef0d44bb3917c5eac6a0e23c7c458f8048e474f Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Fri, 20 Feb 2026 23:28:30 +0000 Subject: [PATCH 04/13] feat(ducpdf): add font fetching and embedding support --- Cargo.lock | 5 +- packages/ducpdf/src/duc2pdf/Cargo.toml | 3 +- packages/ducpdf/src/duc2pdf/fonts.ts | 158 ++++++++++++++++++ packages/ducpdf/src/duc2pdf/index.ts | 66 ++++++-- packages/ducpdf/src/duc2pdf/src/builder.rs | 30 ++++ packages/ducpdf/src/duc2pdf/src/lib.rs | 122 +++++++++++++- .../duc2pdf/src/streaming/stream_elements.rs | 67 ++++++-- 7 files changed, 413 insertions(+), 38 deletions(-) create mode 100644 packages/ducpdf/src/duc2pdf/fonts.ts diff --git a/Cargo.lock b/Cargo.lock index 17cbad4..726945d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,6 +333,7 @@ dependencies = [ "console_log", "duc", "hipdf", + "js-sys", "log", "serde", "serde_json", @@ -591,9 +592,7 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hipdf" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12396f663526a67f084608759c09cdb5ad7f439ae66a0957a508093303342aa" +version = "0.0.0-development" dependencies = [ "jpeg-decoder", "lopdf", diff --git a/packages/ducpdf/src/duc2pdf/Cargo.toml b/packages/ducpdf/src/duc2pdf/Cargo.toml index f3f5766..322cbfd 100644 --- a/packages/ducpdf/src/duc2pdf/Cargo.toml +++ b/packages/ducpdf/src/duc2pdf/Cargo.toml @@ -23,9 +23,10 @@ crate-type = ["cdylib", "rlib"] [dependencies] # Local dev: uses path. Publish: release script updates to crates.io version duc = { version = "0.0.0-development", path = "../../../ducrs" } -hipdf = { version = "1.3.2", features = ["wasm_js"] } +hipdf = { version = "1.3.3", features = ["wasm_js"] } svg2pdf = "0.13.0" wasm-bindgen = "0.2.92" +js-sys = "0.3" chrono = "0.4" bigcolor = { version = "1.2.1" } web-sys = { version = "0.3", features = ["console"] } diff --git a/packages/ducpdf/src/duc2pdf/fonts.ts b/packages/ducpdf/src/duc2pdf/fonts.ts new file mode 100644 index 0000000..5dbb32b --- /dev/null +++ b/packages/ducpdf/src/duc2pdf/fonts.ts @@ -0,0 +1,158 @@ +import type { ExportedDataState } from 'ducjs'; + +// Font families that are bundled in the WASM binary (no need to fetch) +const BUNDLED_FONTS = new Set(['Roboto Mono']); + +// Metadata JSON URL (small file, cacheable via CDN) +const GOOGLE_FONTS_METADATA_URL = 'https://cdn.jsdelivr.net/npm/google-font-metadata@latest/data/google-fonts-v1.json'; + +// In-memory cache for the metadata JSON (fetched once per session) +let metadataCache: Record | null = null; +let metadataFetchPromise: Promise | null> | null = null; + +interface GoogleFontV1Entry { + family: string; + id: string; + weights: number[]; + styles: string[]; + defSubset: string; + variants: Record>>; +} + +/** + * Fetch and cache the google-font-metadata v1 JSON. + */ +async function getGoogleFontMetadata(): Promise | null> { + if (metadataCache) return metadataCache; + if (metadataFetchPromise) return metadataFetchPromise; + + metadataFetchPromise = (async () => { + try { + const res = await fetch(GOOGLE_FONTS_METADATA_URL); + if (!res.ok) return null; + metadataCache = await res.json(); + return metadataCache; + } catch { + return null; + } finally { + metadataFetchPromise = null; + } + })(); + + return metadataFetchPromise; +} + +/** + * Convert a display font family name (e.g. "Roboto Mono") to the metadata key ("roboto-mono"). + */ +const fontFamilyToId = (family: string): string => + family.toLowerCase().replace(/\s+/g, '-'); + +/** + * Look up the TTF (truetype) URL for a font from cached metadata. + */ +function getTrueTypeUrl( + metadata: Record, + family: string, + weight = 400, + style = 'normal', +): string | undefined { + const id = fontFamilyToId(family); + const font = metadata[id]; + if (!font) return undefined; + + const weightVariants = font.variants[String(weight)]; + if (!weightVariants) { + const firstWeight = Object.keys(font.variants)[0]; + if (!firstWeight) return undefined; + const fallback = font.variants[firstWeight]; + const styleVar = fallback?.[style] ?? Object.values(fallback ?? {})[0]; + const subset = styleVar?.[font.defSubset] ?? Object.values(styleVar ?? {})[0]; + return subset?.url?.truetype; + } + + const styleVariants = weightVariants[style] ?? Object.values(weightVariants)[0]; + if (!styleVariants) return undefined; + + const subset = styleVariants[font.defSubset] ?? Object.values(styleVariants)[0]; + return subset?.url?.truetype; +} + +/** + * Validate that a fontFamily string is a real font name (not a numeric ID or empty). + */ +const isValidFontFamily = (ff: unknown): ff is string => + typeof ff === 'string' && ff.length > 0 && !/^\d+$/.test(ff); + +/** + * Extract unique font family names from parsed DUC elements. + */ +function collectFontFamilies(parsed: ExportedDataState): Set { + const families = new Set(); + for (const el of (parsed.elements ?? [])) { + if (el && typeof el === 'object' && 'fontFamily' in el) { + const ff = (el as any).fontFamily; + if (isValidFontFamily(ff)) families.add(ff); + } + if (el && (el as any).type === 'table' && Array.isArray((el as any).cells)) { + for (const cell of (el as any).cells) { + const ff = cell?.fontFamily ?? cell?.style?.fontFamily; + if (isValidFontFamily(ff)) families.add(ff); + } + } + } + const defaultFF = parsed?.localState?.currentItemFontFamily; + if (isValidFontFamily(defaultFF)) families.add(defaultFF); + return families; +} + +/** + * Fetch font data for all detected families in a DUC file. + * Returns fontMap and a list of warning messages for fonts that couldn't be fetched. + */ +export async function fetchFontsForDuc( + parsed: ExportedDataState, +): Promise<{ fontMap: Map; warnings: string[] }> { + const fontMap = new Map(); + const warnings: string[] = []; + const families = collectFontFamilies(parsed); + + const toFetch = [...families].filter(f => !BUNDLED_FONTS.has(f)); + if (toFetch.length === 0) return { fontMap, warnings }; + + const metadata = await getGoogleFontMetadata(); + if (!metadata) { + warnings.push('Could not load Google Fonts metadata. Text will use the default font.'); + return { fontMap, warnings }; + } + + const results = await Promise.allSettled( + toFetch.map(async (family) => { + const ttfUrl = getTrueTypeUrl(metadata, family); + if (!ttfUrl) return { family, bytes: null as Uint8Array | null }; + try { + const res = await fetch(ttfUrl); + if (!res.ok) return { family, bytes: null }; + const buf = await res.arrayBuffer(); + return { family, bytes: buf.byteLength > 1024 ? new Uint8Array(buf) : null }; + } catch { + return { family, bytes: null }; + } + }), + ); + + for (const result of results) { + if (result.status === 'fulfilled') { + const { family, bytes } = result.value; + if (bytes) { + fontMap.set(family, bytes); + } else { + warnings.push(`Font "${family}" could not be fetched and will use the default font.`); + } + } else { + warnings.push('A font fetch failed unexpectedly.'); + } + } + + return { fontMap, warnings }; +} diff --git a/packages/ducpdf/src/duc2pdf/index.ts b/packages/ducpdf/src/duc2pdf/index.ts index b773340..eba0d0e 100644 --- a/packages/ducpdf/src/duc2pdf/index.ts +++ b/packages/ducpdf/src/duc2pdf/index.ts @@ -1,4 +1,10 @@ import { ExportedDataState, getFreeDrawSvgPath, getNormalizedZoom, isFreeDrawElement, parseDuc, serializeDuc, traverseAndUpdatePrecisionValues } from 'ducjs'; +import { fetchFontsForDuc } from './fonts'; + +export interface PdfConversionResult { + data: Uint8Array; + warnings: string[]; +} let wasmModule: any = null; let wasmInitPromise: Promise | null = null; @@ -29,7 +35,9 @@ async function initWasm(): Promise { // Validate that required functions exist on the imported module const requiredFunctions = [ 'convert_duc_to_pdf_rs', - 'convert_duc_to_pdf_crop_wasm' + 'convert_duc_to_pdf_crop_wasm', + 'convert_duc_to_pdf_with_fonts_rs', + 'convert_duc_to_pdf_crop_with_fonts_wasm' ]; for (const fnName of requiredFunctions) { @@ -119,7 +127,8 @@ export async function convertDucToPdf( ducData: Uint8Array, options?: ConversionOptions, debugMode: boolean = false -): Promise { +): Promise { + const fontWarnings: string[] = []; try { // Validate inputs validateInput(ducData, options); @@ -134,6 +143,7 @@ export async function convertDucToPdf( let ducBytes = new Uint8Array(ducData); let viewBackgroundColor; + let normalizedData: ExportedDataState | null = null; try { const latestBlob = new Blob([ducBytes]); @@ -182,6 +192,7 @@ export async function convertDucToPdf( normalized.elements = normalizedElements; viewBackgroundColor = normalized.globalState.viewBackgroundColor; + normalizedData = normalized; // Re-serialize the DUC with normalized values and scope set to 'mm' const serialized = await serializeDuc( @@ -204,8 +215,21 @@ export async function convertDucToPdf( console.warn('DUC parse/serialize normalization failed; using original bytes. Reason:', e); } + // Fetch font data for all detected families (falls back gracefully if offline) + let fontMap = new Map(); + if (normalizedData) { + try { + const result = await fetchFontsForDuc(normalizedData); + fontMap = result.fontMap; + fontWarnings.push(...result.warnings); + } catch (e) { + fontWarnings.push('Font fetching failed. Text will use the default font.'); + } + } + // Call the appropriate WASM function based on options let result: Uint8Array; + const hasFonts = fontMap.size > 0; if (options && (options.offsetX !== undefined || options.offsetY !== undefined)) { // Use crop mode with offset @@ -216,17 +240,33 @@ export async function convertDucToPdf( const heightOption = typeof options.height === 'number' ? options.height : undefined; const backgroundOption = backgroundColor === undefined ? undefined : backgroundColor; - result = wasm.convert_duc_to_pdf_crop_wasm( - ducBytes, - offsetX, - offsetY, - widthOption, - heightOption, - backgroundOption - ); + if (hasFonts) { + result = wasm.convert_duc_to_pdf_crop_with_fonts_wasm( + ducBytes, + offsetX, + offsetY, + widthOption, + heightOption, + backgroundOption, + fontMap + ); + } else { + result = wasm.convert_duc_to_pdf_crop_wasm( + ducBytes, + offsetX, + offsetY, + widthOption, + heightOption, + backgroundOption + ); + } } else { // Standard conversion - result = wasm.convert_duc_to_pdf_rs(ducBytes); + if (hasFonts) { + result = wasm.convert_duc_to_pdf_with_fonts_rs(ducBytes, fontMap); + } else { + result = wasm.convert_duc_to_pdf_rs(ducBytes); + } } // Check if conversion was successful @@ -288,7 +328,7 @@ export async function convertDucToPdf( } } - return result; + return { data: result, warnings: fontWarnings }; } catch (error) { console.error('DUC to PDF conversion error:', error); @@ -309,7 +349,7 @@ export async function convertDucToPdfCrop( offsetY: number, width?: number, height?: number -): Promise { +): Promise { return convertDucToPdf(ducData, { offsetX, offsetY, width, height }); } diff --git a/packages/ducpdf/src/duc2pdf/src/builder.rs b/packages/ducpdf/src/duc2pdf/src/builder.rs index 33a367f..8d035fb 100644 --- a/packages/ducpdf/src/duc2pdf/src/builder.rs +++ b/packages/ducpdf/src/duc2pdf/src/builder.rs @@ -103,6 +103,7 @@ impl DucToPdfBuilder { pub fn new( mut exported_data: ExportedDataState, mut options: ConversionOptions, + font_data: HashMap>, ) -> ConversionResult { let mut document = Document::with_version("1.7"); @@ -179,6 +180,34 @@ impl DucToPdfBuilder { let (primary_font, font_resource_name) = Self::load_primary_font(&mut document, &mut font_manager)?; + // Embed additional fonts provided by the caller (e.g. Google Fonts fetched from CDN) + let mut font_map: HashMap = HashMap::new(); + // Register the primary font under its family name (metadata.family is a String) + let family = primary_font.metadata.family.clone(); + if !family.is_empty() { + font_map.insert(family, (primary_font.clone(), font_resource_name.clone())); + } + font_map.insert("Roboto Mono".to_string(), (primary_font.clone(), font_resource_name.clone())); + + for (family_name, ttf_bytes) in font_data { + match Font::from_bytes(ttf_bytes, Some(format!("{}.ttf", family_name))) { + Ok(font) => { + match font_manager.embed_font(&mut document, font.clone()) { + Ok((_, res_name)) => { + log_info!("Embedded font '{}' as {}", family_name, res_name); + font_map.insert(family_name, (font, res_name)); + } + Err(e) => { + log_warn!("Failed to embed font '{}': {}. Will use fallback.", family_name, e); + } + } + } + Err(e) => { + log_warn!("Failed to parse font '{}': {}. Will use fallback.", family_name, e); + } + } + } + // Create block instances map for duplication support let block_instances: HashMap = context.exported_data .block_instances @@ -201,6 +230,7 @@ impl DucToPdfBuilder { font_resource_name, primary_font, block_instances, + font_map, ), // Default height, will be updated per page resource_streamer: ResourceStreamer::new(), page_ids: Vec::new(), diff --git a/packages/ducpdf/src/duc2pdf/src/lib.rs b/packages/ducpdf/src/duc2pdf/src/lib.rs index cf9e54f..be32e49 100644 --- a/packages/ducpdf/src/duc2pdf/src/lib.rs +++ b/packages/ducpdf/src/duc2pdf/src/lib.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use wasm_bindgen::prelude::*; pub mod builder; @@ -266,17 +267,24 @@ pub fn validate_precision(precision: f64) -> ConversionResult<()> { pub fn convert_duc_to_pdf_with_options( duc_data: &[u8], options: ConversionOptions, +) -> ConversionResult> { + convert_duc_to_pdf_with_fonts_and_options(duc_data, options, HashMap::new()) +} + +/// Main conversion function with options and custom font data +pub fn convert_duc_to_pdf_with_fonts_and_options( + duc_data: &[u8], + options: ConversionOptions, + font_data: HashMap>, ) -> ConversionResult> { let mut normalized_options = options; normalized_options.background_color = normalize_background_color(normalized_options.background_color); - // Parse DUC data let exported_data = duc::parse::parse(duc_data).map_err(|e| ConversionError::InvalidDucData(e.to_string()))?; - // Use the builder to convert - builder::DucToPdfBuilder::new(exported_data, normalized_options)?.build() + builder::DucToPdfBuilder::new(exported_data, normalized_options, font_data)?.build() } /// WASM binding for the main conversion function @@ -408,3 +416,111 @@ pub fn convert_duc_to_pdf_crop_wasm( } } } + +/// Deserialize a JS font map (Map) into a Rust HashMap +fn deserialize_font_map(font_map_js: JsValue) -> HashMap> { + let mut fonts = HashMap::new(); + if font_map_js.is_undefined() || font_map_js.is_null() { + return fonts; + } + + let entries = js_sys::try_iter(&font_map_js) + .ok() + .flatten(); + + if let Some(iter) = entries { + for entry_result in iter { + if let Ok(entry) = entry_result { + let pair = js_sys::Array::from(&entry); + if pair.length() == 2 { + let key = pair.get(0); + let value = pair.get(1); + if let Some(family) = key.as_string() { + let bytes = js_sys::Uint8Array::new(&value); + fonts.insert(family, bytes.to_vec()); + } + } + } + } + } + fonts +} + +/// WASM binding for conversion with custom font data +/// font_map_js: a JS Map mapping font family names to TTF/OTF bytes +#[wasm_bindgen] +pub fn convert_duc_to_pdf_with_fonts_rs(duc_data: &[u8], font_map_js: JsValue) -> Vec { + let font_data = deserialize_font_map(font_map_js); + match convert_duc_to_pdf_with_fonts_and_options(duc_data, ConversionOptions::default(), font_data) { + Ok(pdf_bytes) => pdf_bytes, + Err(e) => { + error_handling::log_error_details(&e, duc_data.len(), "Conversion with fonts (default options)"); + let error_info = error_handling::create_error_info(&e, duc_data.len(), None); + error_handling::error_to_wasm_bytes(&error_info) + } + } +} + +/// WASM binding for crop conversion with custom font data +#[wasm_bindgen] +pub fn convert_duc_to_pdf_crop_with_fonts_wasm( + duc_data: &[u8], + offset_x: f64, + offset_y: f64, + width: Option, + height: Option, + background_color: Option, + font_map_js: JsValue, +) -> Vec { + if let Err(validation_error) = error_handling::validate_basic_inputs( + duc_data, + Some(offset_x), + Some(offset_y), + width, + height, + ) { + let error_info = error_handling::WasmErrorInfo { + error: validation_error.clone(), + error_type: "ValidationError".to_string(), + details: validation_error, + duc_data_length: duc_data.len(), + conversion_context: None, + }; + return error_handling::error_to_wasm_bytes(&error_info); + } + + let normalized_background = normalize_background_color(background_color); + let font_data = deserialize_font_map(font_map_js); + + let options = ConversionOptions { + mode: ConversionMode::Crop { + offset_x, + offset_y, + width, + height, + }, + background_color: normalized_background.clone(), + ..Default::default() + }; + + match convert_duc_to_pdf_with_fonts_and_options(duc_data, options, font_data) { + Ok(pdf_bytes) => pdf_bytes, + Err(e) => { + error_handling::log_error_details(&e, duc_data.len(), "Crop conversion with fonts"); + error_handling::log_crop_details(offset_x, offset_y, width, height); + let crop_options = ConversionOptions { + mode: ConversionMode::Crop { + offset_x, + offset_y, + width, + height, + }, + background_color: normalized_background, + ..Default::default() + }; + let error_info = + error_handling::create_error_info(&e, duc_data.len(), Some(&crop_options)); + error_handling::error_to_wasm_bytes(&error_info) + } + } +} diff --git a/packages/ducpdf/src/duc2pdf/src/streaming/stream_elements.rs b/packages/ducpdf/src/duc2pdf/src/streaming/stream_elements.rs index 3084c7b..b736aaf 100644 --- a/packages/ducpdf/src/duc2pdf/src/streaming/stream_elements.rs +++ b/packages/ducpdf/src/duc2pdf/src/streaming/stream_elements.rs @@ -92,10 +92,12 @@ pub struct ElementStreamer { freedraw_bboxes: HashMap, // freedraw_id -> cached bounding box /// Cache for SVG natural dimensions for scaling calculations svg_dimensions: HashMap, // svg_id -> (width, height) in natural SVG units - /// Font resource name for text rendering + /// Font resource name for text rendering (fallback/primary) font_resource_name: String, - /// Active font used for text rendering and encoding + /// Active font used for text rendering and encoding (fallback/primary) text_font: Font, + /// Map of font family name → (Font, resource_name) for per-element font selection + font_map: HashMap, /// Map of block instances for looking up duplication arrays block_instances: HashMap, /// Whether we should require elements to be marked as "plot" to be rendered @@ -122,6 +124,7 @@ impl ElementStreamer { font_resource_name: String, text_font: Font, block_instances: HashMap, + font_map: HashMap, ) -> Self { Self { style_resolver, @@ -136,6 +139,7 @@ impl ElementStreamer { svg_dimensions: HashMap::new(), font_resource_name, text_font, + font_map, block_instances, render_only_plot_elements: false, ext_gstate_cache: HashMap::new(), @@ -1483,13 +1487,20 @@ impl ElementStreamer { /// Stream text element fn stream_text(&self, text: &DucTextElement) -> ConversionResult> { - use duc::generated::duc::TEXT_ALIGN; + use duc::generated::duc::{TEXT_ALIGN, VERTICAL_ALIGN}; use hipdf::fonts::utils::{create_text_block, TextAlign, WrapStrategy}; let resolved_text = self .style_resolver .resolve_dynamic_fields(&text.text, &DucElementEnum::DucTextElement(text.clone())); + // Resolve font for this element: look up font_map by family, fallback to primary + let (active_font, active_resource_name) = self + .font_map + .get(&text.style.font_family) + .map(|(f, r)| (f, r.as_str())) + .unwrap_or((&self.text_font, &self.font_resource_name)); + // Determine text alignment let align = match text.style.text_align { TEXT_ALIGN::LEFT => TextAlign::Left, @@ -1503,40 +1514,60 @@ impl ElementStreamer { // Determine wrapping strategy let wrap_strategy = if text.auto_resize { - // If auto-resize is true, we might not want to wrap WrapStrategy::Word } else { WrapStrategy::Hybrid }; - // For text positioning in the element's local coordinate system: - // (0, 0) is at the top-left corner of the element after transformation - // PDF text is positioned by baseline, which needs to be below the top - // We want the top of the text to align with the top of the bounding box, - // so the baseline should be at approximately font_size distance from top + let font_size = text.style.font_size as f32; + let element_height = text.base.height as f32; + + // Estimate total text height for vertical alignment + let line_count = { + let max_w = if text.auto_resize { None } else { Some(text.base.width as f32) }; + let paragraphs: Vec<&str> = resolved_text.split('\n').collect(); + let mut count = 0usize; + for para in ¶graphs { + if para.is_empty() { + count += 1; + } else if let Some(w) = max_w { + let wrapped = hipdf::fonts::utils::wrap_text(active_font, para, w, font_size, wrap_strategy); + count += wrapped.len().max(1); + } else { + count += 1; + } + } + count + }; + let total_text_height = font_size + (line_count.saturating_sub(1) as f32) * line_height; - // However, PDF's internal text coordinate system has Y going up from baseline - // So we need to negate to work in our top-down coordinate system - let text_start_y = -(text.style.font_size as f32); + // Apply vertical alignment + let text_start_y = match text.style.vertical_align { + VERTICAL_ALIGN::MIDDLE => { + -(font_size + (element_height - total_text_height) / 2.0) + } + VERTICAL_ALIGN::BOTTOM => { + -(element_height) + } + // TOP or default + _ => -font_size, + }; - // Use the max width from the element's bounding box (unless auto_resize is true) let max_width = if text.auto_resize { None } else { Some(text.base.width as f32) }; - // Use the max height from the element's bounding box let max_height = Some(text.base.height as f32); - // Create multi-line text block with proper wrapping let operations = create_text_block( - &self.font_resource_name, - &self.text_font, + active_resource_name, + active_font, &resolved_text, 0.0, text_start_y, - text.style.font_size as f32, + font_size, max_width, max_height, line_height, From 97a95667edfce835731d9ca60a08b294bef587d9 Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Sat, 21 Feb 2026 00:30:05 +0000 Subject: [PATCH 05/13] refactor(fonts): validate trusted font URLs in fetchFontsForDuc --- packages/ducpdf/src/duc2pdf/fonts.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/ducpdf/src/duc2pdf/fonts.ts b/packages/ducpdf/src/duc2pdf/fonts.ts index 5dbb32b..1d4c488 100644 --- a/packages/ducpdf/src/duc2pdf/fonts.ts +++ b/packages/ducpdf/src/duc2pdf/fonts.ts @@ -4,7 +4,22 @@ import type { ExportedDataState } from 'ducjs'; const BUNDLED_FONTS = new Set(['Roboto Mono']); // Metadata JSON URL (small file, cacheable via CDN) -const GOOGLE_FONTS_METADATA_URL = 'https://cdn.jsdelivr.net/npm/google-font-metadata@latest/data/google-fonts-v1.json'; +const GOOGLE_FONTS_METADATA_URL = 'https://cdn.jsdelivr.net/npm/google-font-metadata@6/data/google-fonts-v1.json'; + +const TRUSTED_FONT_DOMAINS = new Set([ + 'fonts.gstatic.com', + 'fonts.googleapis.com', + 'cdn.jsdelivr.net', +]); + +function isTrustedFontUrl(urlStr: string): boolean { + try { + const url = new URL(urlStr); + return url.protocol === 'https:' && TRUSTED_FONT_DOMAINS.has(url.hostname); + } catch { + return false; + } +} // In-memory cache for the metadata JSON (fetched once per session) let metadataCache: Record | null = null; @@ -129,7 +144,7 @@ export async function fetchFontsForDuc( const results = await Promise.allSettled( toFetch.map(async (family) => { const ttfUrl = getTrueTypeUrl(metadata, family); - if (!ttfUrl) return { family, bytes: null as Uint8Array | null }; + if (!ttfUrl || !isTrustedFontUrl(ttfUrl)) return { family, bytes: null as Uint8Array | null }; try { const res = await fetch(ttfUrl); if (!res.ok) return { family, bytes: null }; From 8e92708a862f1aaec41da5d3eb9c6b200cac349d Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Sat, 21 Feb 2026 00:31:11 +0000 Subject: [PATCH 06/13] fix(ducpy): patch failing tests and workflow silently failing --- .github/workflows/release-ducpy.yml | 1 + .../src/ducpy/builders/element_builders.py | 190 ++-- packages/ducpy/src/ducpy/parse.py | 967 ++++++++++-------- 3 files changed, 685 insertions(+), 473 deletions(-) diff --git a/.github/workflows/release-ducpy.yml b/.github/workflows/release-ducpy.yml index aaaeac6..039fd24 100644 --- a/.github/workflows/release-ducpy.yml +++ b/.github/workflows/release-ducpy.yml @@ -70,6 +70,7 @@ jobs: echo "status=success" >> $GITHUB_OUTPUT else echo "status=failure" >> $GITHUB_OUTPUT + exit 1 fi working-directory: ./packages/ducpy - name: Notify web deployment diff --git a/packages/ducpy/src/ducpy/builders/element_builders.py b/packages/ducpy/src/ducpy/builders/element_builders.py index fe56118..5798aa7 100644 --- a/packages/ducpy/src/ducpy/builders/element_builders.py +++ b/packages/ducpy/src/ducpy/builders/element_builders.py @@ -1,80 +1,122 @@ """ Helper functions for creating DUC elements with a user-friendly API. """ -from math import pi -from typing import List, Optional, Union, TYPE_CHECKING, Any, Dict -import uuid +import math import time +import uuid from dataclasses import dataclass, field +from math import pi +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union + import numpy as np -import math if TYPE_CHECKING: from ..classes.StandardsClass import Standard -from ..classes.ElementsClass import ( - DucFreeDrawEnds, DucImageFilter, DucRectangleElement, DucEllipseElement, DucPolygonElement, - DucElementBase, DucElementStylesBase, ElementWrapper, BoundElement, - DucLinearElement, DucLinearElementBase, DucPoint, DucLine, - DucLineReference, GeometricPoint, DucPointBinding, DucPath, ImageCrop, - PointBindingPoint, DucHead, ElementStroke, ElementBackground, ElementContentBase, StrokeStyle, - DucArrowElement, DucTextElement, DucFrameElement, DucPlotElement, - DucViewportElement, DucStackElementBase, DucStackBase, DucStackLikeStyles, - PlotLayout, DucView, DucPlotStyle, DucViewportStyle, Margins, - DucFreeDrawElement, DucImageElement, DucPdfElement, DucParametricElement, ParametricSource, DucBlockAttributeDefinition, DucBlockAttributeDefinitionEntry, - DucBlock, DucBlockDuplicationArray, StringValueEntry, DucBlockInstance, - DucTableColumn, DucTableRow, DucTableCell, DucTableCellSpan, DucTableAutoSize, - DucTableElement, DucTableStyle, DucTableCellStyle, DucTextStyle, DucLayer, DucLayerOverrides, - DucRegion, DucDocElement, DucDocStyle, ParagraphFormatting, StackFormat, StackFormatProperties, - TextColumn, ColumnLayout, DucTextDynamicPart, DucDimensionElement, DucDimensionStyle, - DimensionDefinitionPoints, DimensionBindings, DimensionLineStyle, DimensionExtLineStyle, - DimensionSymbolStyle, DimensionToleranceStyle, DimensionFitStyle, DimensionBaselineData, - DimensionContinueData, DucLeaderElement, LeaderContent, LeaderTextBlockContent, LeaderBlockContent, - DucFeatureControlFrameElement, ToleranceClause, FeatureControlFrameSegment, DatumReference, - FCFFrameModifiers, FCFBetweenModifier, FCFProjectedZoneModifier, FCFDatumDefinition, FCFSegmentRow, - DucMermaidElement, DucEmbeddableElement, DucXRayElement, DucXRayStyle, FCFLayoutStyle, FCFSymbolStyle, FCFDatumStyle, DucFeatureControlFrameStyle, - DucLeaderStyle, BLOCK_ATTACHMENT, DucTableColumnEntry, DucTableRowEntry, DucTableCellEntry -) -from .style_builders import create_simple_styles, create_text_style, create_paragraph_formatting, create_stack_format_properties, create_stack_format, create_doc_style, create_text_column, create_column_layout -from ducpy.utils import generate_random_id, DEFAULT_SCOPE, DEFAULT_STROKE_COLOR, DEFAULT_FILL_COLOR, DEFAULT_STROKE_WIDTH -from ducpy.utils.rand_utils import random_versioning + +import random +import time + +from ducpy.Duc.AXIS import AXIS +from ducpy.Duc.BLOCK_ATTACHMENT import BLOCK_ATTACHMENT from ducpy.Duc.BOOLEAN_OPERATION import BOOLEAN_OPERATION -from ducpy.Duc.TABLE_CELL_ALIGNMENT import TABLE_CELL_ALIGNMENT -from ducpy.Duc.TABLE_FLOW_DIRECTION import TABLE_FLOW_DIRECTION -from ducpy.Duc.TEXT_ALIGN import TEXT_ALIGN -from ducpy.Duc.VERTICAL_ALIGN import VERTICAL_ALIGN -from ducpy.Duc.LINE_SPACING_TYPE import LINE_SPACING_TYPE -from ducpy.Duc.TEXT_FLOW_DIRECTION import TEXT_FLOW_DIRECTION from ducpy.Duc.COLUMN_TYPE import COLUMN_TYPE -from ducpy.Duc.STACKED_TEXT_ALIGN import STACKED_TEXT_ALIGN +from ducpy.Duc.DIMENSION_FIT_RULE import DIMENSION_FIT_RULE +from ducpy.Duc.DIMENSION_TEXT_PLACEMENT import DIMENSION_TEXT_PLACEMENT from ducpy.Duc.DIMENSION_TYPE import DIMENSION_TYPE -from ducpy.Duc.AXIS import AXIS +from ducpy.Duc.FEATURE_MODIFIER import FEATURE_MODIFIER from ducpy.Duc.GDT_SYMBOL import GDT_SYMBOL +from ducpy.Duc.IMAGE_STATUS import IMAGE_STATUS from ducpy.Duc.LEADER_CONTENT_TYPE import LEADER_CONTENT_TYPE -from ducpy.Duc.TOLERANCE_ZONE_TYPE import TOLERANCE_ZONE_TYPE +from ducpy.Duc.LINE_SPACING_TYPE import LINE_SPACING_TYPE +from ducpy.Duc.MARK_ELLIPSE_CENTER import MARK_ELLIPSE_CENTER from ducpy.Duc.MATERIAL_CONDITION import MATERIAL_CONDITION -from ducpy.Duc.FEATURE_MODIFIER import FEATURE_MODIFIER from ducpy.Duc.PARAMETRIC_SOURCE_TYPE import PARAMETRIC_SOURCE_TYPE -from ducpy.Duc.VIEWPORT_SHADE_PLOT import VIEWPORT_SHADE_PLOT -from ducpy.Duc.IMAGE_STATUS import IMAGE_STATUS -from ducpy.Duc.STROKE_PREFERENCE import STROKE_PREFERENCE -from ducpy.Duc.STROKE_PLACEMENT import STROKE_PLACEMENT +from ducpy.Duc.STACKED_TEXT_ALIGN import STACKED_TEXT_ALIGN from ducpy.Duc.STROKE_CAP import STROKE_CAP from ducpy.Duc.STROKE_JOIN import STROKE_JOIN -from ducpy.Duc.MARK_ELLIPSE_CENTER import MARK_ELLIPSE_CENTER -from ducpy.Duc.DIMENSION_FIT_RULE import DIMENSION_FIT_RULE -from ducpy.Duc.DIMENSION_TEXT_PLACEMENT import DIMENSION_TEXT_PLACEMENT +from ducpy.Duc.STROKE_PLACEMENT import STROKE_PLACEMENT +from ducpy.Duc.STROKE_PREFERENCE import STROKE_PREFERENCE +from ducpy.Duc.TABLE_CELL_ALIGNMENT import TABLE_CELL_ALIGNMENT +from ducpy.Duc.TABLE_FLOW_DIRECTION import TABLE_FLOW_DIRECTION +from ducpy.Duc.TEXT_ALIGN import TEXT_ALIGN +from ducpy.Duc.TEXT_FLOW_DIRECTION import TEXT_FLOW_DIRECTION from ducpy.Duc.TOLERANCE_DISPLAY import TOLERANCE_DISPLAY +from ducpy.Duc.TOLERANCE_ZONE_TYPE import TOLERANCE_ZONE_TYPE from ducpy.Duc.VERTICAL_ALIGN import VERTICAL_ALIGN -from ducpy.Duc.BLOCK_ATTACHMENT import BLOCK_ATTACHMENT +from ducpy.Duc.VIEWPORT_SHADE_PLOT import VIEWPORT_SHADE_PLOT +from ducpy.utils import (DEFAULT_FILL_COLOR, DEFAULT_SCOPE, + DEFAULT_STROKE_COLOR, DEFAULT_STROKE_WIDTH, + generate_random_id) +from ducpy.utils.rand_utils import random_versioning + +from ..classes.ElementsClass import (BLOCK_ATTACHMENT, BoundElement, + ColumnLayout, DatumReference, + DimensionBaselineData, DimensionBindings, + DimensionContinueData, + DimensionDefinitionPoints, + DimensionExtLineStyle, DimensionFitStyle, + DimensionLineStyle, DimensionSymbolStyle, + DimensionToleranceStyle, + DocumentGridConfig, DucArrowElement, + DucBlock, DucBlockAttributeDefinition, + DucBlockAttributeDefinitionEntry, + DucBlockDuplicationArray, + DucBlockInstance, DucDimensionElement, + DucDimensionStyle, DucDocElement, + DucDocStyle, DucElementBase, + DucElementStylesBase, DucEllipseElement, + DucEmbeddableElement, + DucFeatureControlFrameElement, + DucFeatureControlFrameStyle, + DucFrameElement, DucFreeDrawElement, + DucFreeDrawEnds, DucHead, DucImageElement, + DucImageFilter, DucLayer, + DucLayerOverrides, DucLeaderElement, + DucLeaderStyle, DucLine, DucLinearElement, + DucLinearElementBase, DucLineReference, + DucMermaidElement, DucParametricElement, + DucPath, DucPdfElement, DucPlotElement, + DucPlotStyle, DucPoint, DucPointBinding, + DucPolygonElement, DucRectangleElement, + DucRegion, DucStackBase, + DucStackElementBase, DucStackLikeStyles, + DucTableAutoSize, DucTableCell, + DucTableCellEntry, DucTableCellSpan, + DucTableCellStyle, DucTableColumn, + DucTableColumnEntry, DucTableElement, + DucTableRow, DucTableRowEntry, + DucTableStyle, DucTextDynamicPart, + DucTextElement, DucTextStyle, DucView, + DucViewportElement, DucViewportStyle, + DucXRayElement, DucXRayStyle, + ElementBackground, ElementContentBase, + ElementStroke, ElementWrapper, + FCFBetweenModifier, FCFDatumDefinition, + FCFDatumStyle, FCFFrameModifiers, + FCFLayoutStyle, FCFProjectedZoneModifier, + FCFSegmentRow, FCFSymbolStyle, + FeatureControlFrameSegment, + GeometricPoint, ImageCrop, + LeaderBlockContent, LeaderContent, + LeaderTextBlockContent, Margins, + ParagraphFormatting, ParametricSource, + PlotLayout, PointBindingPoint, + StackFormat, StackFormatProperties, + StringValueEntry, StrokeStyle, TextColumn, + ToleranceClause) +from .style_builders import (create_column_layout, create_doc_style, + create_paragraph_formatting, create_simple_styles, + create_stack_format, + create_stack_format_properties, + create_text_column, create_text_style) -import random -import time def _create_element_wrapper(element_class, base_params, element_params, explicit_properties_override=None): """Helper function to create an ElementWrapper with the given parameters.""" # Create the base DucElementBase from ducpy.utils import generate_random_id from ducpy.utils.rand_utils import random_versioning + # Generate ID if not provided if not base_params.get('id'): base_params['id'] = generate_random_id() @@ -279,7 +321,8 @@ def _create_element_wrapper(element_class, base_params, element_params, explicit end_binding=element_params.get('end_binding') ) # Create leader style - from ducpy.builders.style_builders import create_simple_styles, create_text_style + from ducpy.builders.style_builders import (create_simple_styles, + create_text_style) leader_style = element_params.get('style') if leader_style is None: text_style = create_text_style() @@ -300,7 +343,10 @@ def _create_element_wrapper(element_class, base_params, element_params, explicit ) elif element_class == DucDocElement: # Create doc style - from ducpy.builders.style_builders import create_simple_styles, create_text_style, create_paragraph_formatting, create_stack_format + from ducpy.builders.style_builders import (create_paragraph_formatting, + create_simple_styles, + create_stack_format, + create_text_style) doc_style = element_params.get('style') if doc_style is None: text_style = create_text_style() @@ -314,9 +360,17 @@ def _create_element_wrapper(element_class, base_params, element_params, explicit # Create column layout columns_layout = element_params.get('columns') if columns_layout is None: - from ducpy.builders.style_builders import create_text_column, create_column_layout + from ducpy.builders.style_builders import (create_column_layout, + create_text_column) text_column = create_text_column(width=100.0) columns_layout = create_column_layout(definitions=[text_column]) + default_grid_config = DocumentGridConfig( + columns=1, + gap_x=0.0, + gap_y=0.0, + align_items=0, + first_page_alone=False, + ) specific_element = element_class( base=base_element, style=doc_style, @@ -324,11 +378,16 @@ def _create_element_wrapper(element_class, base_params, element_params, explicit dynamic=element_params.get('dynamic', []), columns=columns_layout, auto_resize=element_params.get('auto_resize', False), - flow_direction=element_params.get('flow_direction') + flow_direction=element_params.get('flow_direction'), + file_id=element_params.get('file_id'), + grid_config=element_params.get('grid_config', default_grid_config), ) elif element_class == DucDimensionElement: # Create dimension style - from ducpy.builders.style_builders import create_simple_styles, create_text_style, create_solid_content, create_stroke + from ducpy.builders.style_builders import (create_simple_styles, + create_solid_content, + create_stroke, + create_text_style) dimension_style = element_params.get('style') if dimension_style is None: text_style = create_text_style() @@ -406,7 +465,8 @@ def _create_element_wrapper(element_class, base_params, element_params, explicit ) elif element_class == DucFeatureControlFrameElement: # Create feature control frame style - from ducpy.builders.style_builders import create_simple_styles, create_text_style + from ducpy.builders.style_builders import (create_simple_styles, + create_text_style) fcf_style = element_params.get('style') if fcf_style is None: text_style = create_text_style() @@ -1108,10 +1168,22 @@ def with_file_id(self, file_id: str): self.extra["file_id"] = file_id return self + def with_grid_config(self, grid_config: DocumentGridConfig): + self.extra["grid_config"] = grid_config + return self + def build(self) -> ElementWrapper: base_params = self.base.__dict__.copy() + default_grid_config = DocumentGridConfig( + columns=1, + gap_x=0.0, + gap_y=0.0, + align_items=0, + first_page_alone=False, + ) element_params = { "file_id": self.extra.get('file_id'), + "grid_config": self.extra.get('grid_config', default_grid_config), } return _create_element_wrapper( DucPdfElement, @@ -1306,7 +1378,8 @@ def build(self) -> ElementWrapper: # Ensure style is always set style = self.extra.get('style') if style is None: - from ducpy.builders.style_builders import create_simple_styles, create_text_style + from ducpy.builders.style_builders import (create_simple_styles, + create_text_style) style = DucTableStyle( header_row_style=DucTableCellStyle( text_style=create_text_style(), @@ -2004,7 +2077,8 @@ def build(self) -> ElementWrapper: # Create leader style with default dogleg leader_style = element_params.get('style') if leader_style is None: - from ducpy.builders.style_builders import create_simple_styles, create_text_style + from ducpy.builders.style_builders import (create_simple_styles, + create_text_style) text_style = create_text_style() leader_style = DucLeaderStyle( text_style=text_style, diff --git a/packages/ducpy/src/ducpy/parse.py b/packages/ducpy/src/ducpy/parse.py index 2a6f3c1..c3bcf17 100644 --- a/packages/ducpy/src/ducpy/parse.py +++ b/packages/ducpy/src/ducpy/parse.py @@ -3,441 +3,575 @@ from __future__ import annotations -import json import gzip -from typing import List, Dict, Optional, Union, Any, IO +import json +from typing import IO, Any, Dict, List, Optional, Union import flatbuffers -from flatbuffers.table import Table - -from ducpy.classes.DataStateClass import ( - ExportedDataState as DS_ExportedDataState, - DictionaryEntry as DS_DictionaryEntry, - DucLocalState as DS_DucLocalState, - DucGlobalState as DS_DucGlobalState, - VersionGraph as DS_VersionGraph, - DucExternalFileEntry as DS_DucExternalFileEntry, - DucExternalFileData as DS_DucExternalFileData, - Checkpoint as DS_Checkpoint, - Delta as DS_Delta, - VersionBase as DS_VersionBase, - JSONPatchOperation as DS_JSONPatchOperation, - VersionGraphMetadata as DS_VersionGraphMetadata, - DisplayPrecision as DS_DisplayPrecision, -) -from ducpy.classes.ElementsClass import ( - ElementWrapper as DS_ElementWrapper, - DucBlockCollectionEntry as DS_DucBlockCollectionEntry, - DucRectangleElement as DS_DucRectangleElement, - DucPolygonElement as DS_DucPolygonElement, - DucEllipseElement as DS_DucEllipseElement, - DucEmbeddableElement as DS_DucEmbeddableElement, - DucPdfElement as DS_DucPdfElement, - DucMermaidElement as DS_DucMermaidElement, - DucTableElement as DS_DucTableElement, - DucImageElement as DS_DucImageElement, - DucTextElement as DS_DucTextElement, - DucLinearElement as DS_DucLinearElement, - DucArrowElement as DS_DucArrowElement, - DucFreeDrawElement as DS_DucFreeDrawElement, - DucBlockInstance as DS_DucBlockInstance, - DucFrameElement as DS_DucFrameElement, - DucPlotElement as DS_DucPlotElement, - DucViewportElement as DS_DucViewportElement, - DucXRayElement as DS_DucXRayElement, - DucLeaderElement as DS_DucLeaderElement, - DucDimensionElement as DS_DucDimensionElement, - DucFeatureControlFrameElement as DS_DucFeatureControlFrameElement, - DucDocElement as DS_DucDocElement, - DucParametricElement as DS_DucParametricElement, - DucModelElement as DS_DucModelElement, - DucBlock as DS_DucBlock, - DucBlockCollection as DS_DucBlockCollection, - DucBlockMetadata as DS_DucBlockMetadata, - DucGroup as DS_DucGroup, - DucRegion as DS_DucRegion, - DucLayer as DS_DucLayer, - DocumentGridConfig as DS_DocumentGridConfig, - ElementBackground as DS_ElementBackground, - ElementStroke as DS_ElementStroke, - GeometricPoint as DS_GeometricPoint, - BoundElement as DS_BoundElement, - DucPoint as DS_DucPoint, - DucHead as DS_DucHead, - DucLine as DS_DucLine, - DucLineReference as DS_DucLineReference, - DucPath as DS_DucPath, - DucLinearElementBase as DS_DucLinearElementBase, - DucElementBase as DS_DucElementBase, - DucElementStylesBase as DS_DucElementStylesBase, - ElementContentBase as DS_ElementContentBase, - StrokeStyle as DS_StrokeStyle, - StrokeSides as DS_StrokeSides, - DucStackLikeStyles as DS_DucStackLikeStyles, - DucStackBase as DS_DucStackBase, - DucStackElementBase as DS_DucStackElementBase, - LineSpacing as DS_LineSpacing, - DucTextStyle as DS_DucTextStyle, - DucTableCellStyle as DS_DucTableCellStyle, - DucTableStyle as DS_DucTableStyle, - DucLeaderStyle as DS_DucLeaderStyle, - DimensionToleranceStyle as DS_DimensionToleranceStyle, - DimensionFitStyle as DS_DimensionFitStyle, - DimensionLineStyle as DS_DimensionLineStyle, - DimensionExtLineStyle as DS_DimensionExtLineStyle, - DimensionSymbolStyle as DS_DimensionSymbolStyle, - DucDimensionStyle as DS_DucDimensionStyle, - FCFLayoutStyle as DS_FCFLayoutStyle, - FCFSymbolStyle as DS_FCFSymbolStyle, - FCFDatumStyle as DS_FCFDatumStyle, - DucFeatureControlFrameStyle as DS_DucFeatureControlFrameStyle, - ParagraphFormatting as DS_ParagraphFormatting, - StackFormatProperties as DS_StackFormatProperties, - StackFormat as DS_StackFormat, - DucDocStyle as DS_DucDocStyle, - DucViewportStyle as DS_DucViewportStyle, - DucPlotStyle as DS_DucPlotStyle, - DucXRayStyle as DS_DucXRayStyle, - DucTableColumn as DS_DucTableColumn, - DucTableRow as DS_DucTableRow, - DucTableCell as DS_DucTableCell, - DucTableColumnEntry as DS_DucTableColumnEntry, - DucTableRowEntry as DS_DucTableRowEntry, - DucTableCellEntry as DS_DucTableCellEntry, - DucTableCellSpan as DS_DucTableCellSpan, - DucTableAutoSize as DS_DucTableAutoSize, - ImageCrop as DS_ImageCrop, - DucTextDynamicElementSource as DS_DucTextDynamicElementSource, - DucTextDynamicDictionarySource as DS_DucTextDynamicDictionarySource, - DucTextDynamicSource as DS_DucTextDynamicSource, - DucTextDynamicPart as DS_DucTextDynamicPart, - PointBindingPoint as DS_PointBindingPoint, - DucPointBinding as DS_DucPointBinding, - DucFreeDrawEnds as DS_DucFreeDrawEnds, - StringValueEntry as DS_StringValueEntry, - DucBlockDuplicationArray as DS_DucBlockDuplicationArray, - PlotLayout as DS_PlotLayout, - LeaderTextBlockContent as DS_LeaderTextBlockContent, - LeaderBlockContent as DS_LeaderBlockContent, - LeaderContent as DS_LeaderContent, - DucBlockAttributeDefinition as DS_DucBlockAttributeDefinition, - DucBlockAttributeDefinitionEntry as DS_DucBlockAttributeDefinitionEntry, - DimensionDefinitionPoints as DS_DimensionDefinitionPoints, - DimensionBindings as DS_DimensionBindings, - DimensionBaselineData as DS_DimensionBaselineData, - DimensionContinueData as DS_DimensionContinueData, - DatumReference as DS_DatumReference, - ToleranceClause as DS_ToleranceClause, - FeatureControlFrameSegment as DS_FeatureControlFrameSegment, - FCFSegmentRow as DS_FCFSegmentRow, - FCFBetweenModifier as DS_FCFBetweenModifier, - FCFProjectedZoneModifier as DS_FCFProjectedZoneModifier, - FCFFrameModifiers as DS_FCFFrameModifiers, - FCFDatumDefinition as DS_FCFDatumDefinition, - TextColumn as DS_TextColumn, - ColumnLayout as DS_ColumnLayout, - DucCommonStyle as DS_DucCommonStyle, - ParametricSource as DS_ParametricSource, - DucLayerOverrides as DS_DucLayerOverrides, - DucView as DS_DucView, - DucUcs as DS_DucUcs, - TilingProperties as DS_TilingProperties, - HatchPatternLine as DS_HatchPatternLine, - CustomHatchPattern as DS_CustomHatchPattern, - DucHatchStyle as DS_DucHatchStyle, - DucImageFilter as DS_DucImageFilter, -) -from ducpy.classes.StandardsClass import ( - Standard as DS_Standard, - Identifier as DS_Identifier, - GridSettings as DS_GridSettings, - GridStyle as DS_GridStyle, - PolarGridSettings as DS_PolarGridSettings, - IsometricGridSettings as DS_IsometricGridSettings, - SnapSettings as DS_SnapSettings, - SnapOverride as DS_SnapOverride, - DynamicSnapSettings as DS_DynamicSnapSettings, - PolarTrackingSettings as DS_PolarTrackingSettings, - TrackingLineStyle as DS_TrackingLineStyle, - LayerSnapFilters as DS_LayerSnapFilters, - SnapMarkerStyle as DS_SnapMarkerStyle, - SnapMarkerStyleEntry as DS_SnapMarkerStyleEntry, - SnapMarkerSettings as DS_SnapMarkerSettings, - UnitSystemBase as DS_UnitSystemBase, - LinearUnitSystem as DS_LinearUnitSystem, - AngularUnitSystem as DS_AngularUnitSystem, - AlternateUnits as DS_AlternateUnits, - PrimaryUnits as DS_PrimaryUnits, - StandardUnits as DS_StandardUnits, - UnitPrecision as DS_UnitPrecision, - StandardOverrides as DS_StandardOverrides, - IdentifiedCommonStyle as DS_IdentifiedCommonStyle, - IdentifiedStackLikeStyle as DS_IdentifiedStackLikeStyle, - IdentifiedTextStyle as DS_IdentifiedTextStyle, - IdentifiedDimensionStyle as DS_IdentifiedDimensionStyle, - IdentifiedLeaderStyle as DS_IdentifiedLeaderStyle, - IdentifiedFCFStyle as DS_IdentifiedFCFStyle, - IdentifiedTableStyle as DS_IdentifiedTableStyle, - IdentifiedDocStyle as DS_IdentifiedDocStyle, - IdentifiedViewportStyle as DS_IdentifiedViewportStyle, - IdentifiedHatchStyle as DS_IdentifiedHatchStyle, - IdentifiedXRayStyle as DS_IdentifiedXRayStyle, - StandardStyles as DS_StandardStyles, - IdentifiedGridSettings as DS_IdentifiedGridSettings, - IdentifiedSnapSettings as DS_IdentifiedSnapSettings, - IdentifiedUcs as DS_IdentifiedUcs, - IdentifiedView as DS_IdentifiedView, - StandardViewSettings as DS_StandardViewSettings, - DimensionValidationRules as DS_DimensionValidationRules, - LayerValidationRules as DS_LayerValidationRules, - StandardValidation as DS_StandardValidation, -) - -# Enums (we only need types for Optional typing and constants occasionally) -from ducpy.Duc.TEXT_ALIGN import TEXT_ALIGN -from ducpy.Duc.VERTICAL_ALIGN import VERTICAL_ALIGN -from ducpy.Duc.LINE_SPACING_TYPE import LINE_SPACING_TYPE -from ducpy.Duc.STROKE_PREFERENCE import STROKE_PREFERENCE -from ducpy.Duc.STROKE_CAP import STROKE_CAP -from ducpy.Duc.STROKE_JOIN import STROKE_JOIN -from ducpy.Duc.STROKE_PLACEMENT import STROKE_PLACEMENT -from ducpy.Duc.STROKE_SIDE_PREFERENCE import STROKE_SIDE_PREFERENCE -from ducpy.Duc.ELEMENT_CONTENT_PREFERENCE import ELEMENT_CONTENT_PREFERENCE -from ducpy.Duc.BLENDING import BLENDING -from ducpy.Duc.LINE_HEAD import LINE_HEAD +from ducpy.classes.DataStateClass import Checkpoint as DS_Checkpoint +from ducpy.classes.DataStateClass import Delta as DS_Delta +from ducpy.classes.DataStateClass import DictionaryEntry as DS_DictionaryEntry +from ducpy.classes.DataStateClass import \ + DisplayPrecision as DS_DisplayPrecision +from ducpy.classes.DataStateClass import \ + DucExternalFileData as DS_DucExternalFileData +from ducpy.classes.DataStateClass import \ + DucExternalFileEntry as DS_DucExternalFileEntry +from ducpy.classes.DataStateClass import DucGlobalState as DS_DucGlobalState +from ducpy.classes.DataStateClass import DucLocalState as DS_DucLocalState +from ducpy.classes.DataStateClass import \ + ExportedDataState as DS_ExportedDataState +from ducpy.classes.DataStateClass import \ + JSONPatchOperation as DS_JSONPatchOperation +from ducpy.classes.DataStateClass import VersionBase as DS_VersionBase +from ducpy.classes.DataStateClass import VersionGraph as DS_VersionGraph +from ducpy.classes.DataStateClass import \ + VersionGraphMetadata as DS_VersionGraphMetadata +from ducpy.classes.ElementsClass import BoundElement as DS_BoundElement +from ducpy.classes.ElementsClass import ColumnLayout as DS_ColumnLayout +from ducpy.classes.ElementsClass import \ + CustomHatchPattern as DS_CustomHatchPattern +from ducpy.classes.ElementsClass import DatumReference as DS_DatumReference +from ducpy.classes.ElementsClass import \ + DimensionBaselineData as DS_DimensionBaselineData +from ducpy.classes.ElementsClass import \ + DimensionBindings as DS_DimensionBindings +from ducpy.classes.ElementsClass import \ + DimensionContinueData as DS_DimensionContinueData +from ducpy.classes.ElementsClass import \ + DimensionDefinitionPoints as DS_DimensionDefinitionPoints +from ducpy.classes.ElementsClass import \ + DimensionExtLineStyle as DS_DimensionExtLineStyle +from ducpy.classes.ElementsClass import \ + DimensionFitStyle as DS_DimensionFitStyle +from ducpy.classes.ElementsClass import \ + DimensionLineStyle as DS_DimensionLineStyle +from ducpy.classes.ElementsClass import \ + DimensionSymbolStyle as DS_DimensionSymbolStyle +from ducpy.classes.ElementsClass import \ + DimensionToleranceStyle as DS_DimensionToleranceStyle +from ducpy.classes.ElementsClass import \ + DocumentGridConfig as DS_DocumentGridConfig +from ducpy.classes.ElementsClass import DucArrowElement as DS_DucArrowElement +from ducpy.classes.ElementsClass import DucBlock as DS_DucBlock +from ducpy.classes.ElementsClass import \ + DucBlockAttributeDefinition as DS_DucBlockAttributeDefinition +from ducpy.classes.ElementsClass import \ + DucBlockAttributeDefinitionEntry as DS_DucBlockAttributeDefinitionEntry +from ducpy.classes.ElementsClass import \ + DucBlockCollection as DS_DucBlockCollection +from ducpy.classes.ElementsClass import \ + DucBlockCollectionEntry as DS_DucBlockCollectionEntry +from ducpy.classes.ElementsClass import \ + DucBlockDuplicationArray as DS_DucBlockDuplicationArray +from ducpy.classes.ElementsClass import DucBlockInstance as DS_DucBlockInstance +from ducpy.classes.ElementsClass import DucBlockMetadata as DS_DucBlockMetadata +from ducpy.classes.ElementsClass import DucCommonStyle as DS_DucCommonStyle +from ducpy.classes.ElementsClass import \ + DucDimensionElement as DS_DucDimensionElement +from ducpy.classes.ElementsClass import \ + DucDimensionStyle as DS_DucDimensionStyle +from ducpy.classes.ElementsClass import DucDocElement as DS_DucDocElement +from ducpy.classes.ElementsClass import DucDocStyle as DS_DucDocStyle +from ducpy.classes.ElementsClass import DucElementBase as DS_DucElementBase +from ducpy.classes.ElementsClass import \ + DucElementStylesBase as DS_DucElementStylesBase +from ducpy.classes.ElementsClass import \ + DucEllipseElement as DS_DucEllipseElement +from ducpy.classes.ElementsClass import \ + DucEmbeddableElement as DS_DucEmbeddableElement +from ducpy.classes.ElementsClass import \ + DucFeatureControlFrameElement as DS_DucFeatureControlFrameElement +from ducpy.classes.ElementsClass import \ + DucFeatureControlFrameStyle as DS_DucFeatureControlFrameStyle +from ducpy.classes.ElementsClass import DucFrameElement as DS_DucFrameElement +from ducpy.classes.ElementsClass import \ + DucFreeDrawElement as DS_DucFreeDrawElement +from ducpy.classes.ElementsClass import DucFreeDrawEnds as DS_DucFreeDrawEnds +from ducpy.classes.ElementsClass import DucGroup as DS_DucGroup +from ducpy.classes.ElementsClass import DucHatchStyle as DS_DucHatchStyle +from ducpy.classes.ElementsClass import DucHead as DS_DucHead +from ducpy.classes.ElementsClass import DucImageElement as DS_DucImageElement +from ducpy.classes.ElementsClass import DucImageFilter as DS_DucImageFilter +from ducpy.classes.ElementsClass import DucLayer as DS_DucLayer +from ducpy.classes.ElementsClass import \ + DucLayerOverrides as DS_DucLayerOverrides +from ducpy.classes.ElementsClass import DucLeaderElement as DS_DucLeaderElement +from ducpy.classes.ElementsClass import DucLeaderStyle as DS_DucLeaderStyle +from ducpy.classes.ElementsClass import DucLine as DS_DucLine +from ducpy.classes.ElementsClass import DucLinearElement as DS_DucLinearElement +from ducpy.classes.ElementsClass import \ + DucLinearElementBase as DS_DucLinearElementBase +from ducpy.classes.ElementsClass import DucLineReference as DS_DucLineReference +from ducpy.classes.ElementsClass import \ + DucMermaidElement as DS_DucMermaidElement +from ducpy.classes.ElementsClass import DucModelElement as DS_DucModelElement +from ducpy.classes.ElementsClass import \ + DucParametricElement as DS_DucParametricElement +from ducpy.classes.ElementsClass import DucPath as DS_DucPath +from ducpy.classes.ElementsClass import DucPdfElement as DS_DucPdfElement +from ducpy.classes.ElementsClass import DucPlotElement as DS_DucPlotElement +from ducpy.classes.ElementsClass import DucPlotStyle as DS_DucPlotStyle +from ducpy.classes.ElementsClass import DucPoint as DS_DucPoint +from ducpy.classes.ElementsClass import DucPointBinding as DS_DucPointBinding +from ducpy.classes.ElementsClass import \ + DucPolygonElement as DS_DucPolygonElement +from ducpy.classes.ElementsClass import \ + DucRectangleElement as DS_DucRectangleElement +from ducpy.classes.ElementsClass import DucRegion as DS_DucRegion +from ducpy.classes.ElementsClass import DucStackBase as DS_DucStackBase +from ducpy.classes.ElementsClass import \ + DucStackElementBase as DS_DucStackElementBase +from ducpy.classes.ElementsClass import \ + DucStackLikeStyles as DS_DucStackLikeStyles +from ducpy.classes.ElementsClass import DucTableAutoSize as DS_DucTableAutoSize +from ducpy.classes.ElementsClass import DucTableCell as DS_DucTableCell +from ducpy.classes.ElementsClass import \ + DucTableCellEntry as DS_DucTableCellEntry +from ducpy.classes.ElementsClass import DucTableCellSpan as DS_DucTableCellSpan +from ducpy.classes.ElementsClass import \ + DucTableCellStyle as DS_DucTableCellStyle +from ducpy.classes.ElementsClass import DucTableColumn as DS_DucTableColumn +from ducpy.classes.ElementsClass import \ + DucTableColumnEntry as DS_DucTableColumnEntry +from ducpy.classes.ElementsClass import DucTableElement as DS_DucTableElement +from ducpy.classes.ElementsClass import DucTableRow as DS_DucTableRow +from ducpy.classes.ElementsClass import DucTableRowEntry as DS_DucTableRowEntry +from ducpy.classes.ElementsClass import DucTableStyle as DS_DucTableStyle +from ducpy.classes.ElementsClass import \ + DucTextDynamicDictionarySource as DS_DucTextDynamicDictionarySource +from ducpy.classes.ElementsClass import \ + DucTextDynamicElementSource as DS_DucTextDynamicElementSource +from ducpy.classes.ElementsClass import \ + DucTextDynamicPart as DS_DucTextDynamicPart +from ducpy.classes.ElementsClass import \ + DucTextDynamicSource as DS_DucTextDynamicSource +from ducpy.classes.ElementsClass import DucTextElement as DS_DucTextElement +from ducpy.classes.ElementsClass import DucTextStyle as DS_DucTextStyle +from ducpy.classes.ElementsClass import DucUcs as DS_DucUcs +from ducpy.classes.ElementsClass import DucView as DS_DucView +from ducpy.classes.ElementsClass import \ + DucViewportElement as DS_DucViewportElement +from ducpy.classes.ElementsClass import DucViewportStyle as DS_DucViewportStyle +from ducpy.classes.ElementsClass import DucXRayElement as DS_DucXRayElement +from ducpy.classes.ElementsClass import DucXRayStyle as DS_DucXRayStyle +from ducpy.classes.ElementsClass import \ + ElementBackground as DS_ElementBackground +from ducpy.classes.ElementsClass import \ + ElementContentBase as DS_ElementContentBase +from ducpy.classes.ElementsClass import ElementStroke as DS_ElementStroke +from ducpy.classes.ElementsClass import ElementWrapper as DS_ElementWrapper +from ducpy.classes.ElementsClass import \ + FCFBetweenModifier as DS_FCFBetweenModifier +from ducpy.classes.ElementsClass import \ + FCFDatumDefinition as DS_FCFDatumDefinition +from ducpy.classes.ElementsClass import FCFDatumStyle as DS_FCFDatumStyle +from ducpy.classes.ElementsClass import \ + FCFFrameModifiers as DS_FCFFrameModifiers +from ducpy.classes.ElementsClass import FCFLayoutStyle as DS_FCFLayoutStyle +from ducpy.classes.ElementsClass import \ + FCFProjectedZoneModifier as DS_FCFProjectedZoneModifier +from ducpy.classes.ElementsClass import FCFSegmentRow as DS_FCFSegmentRow +from ducpy.classes.ElementsClass import FCFSymbolStyle as DS_FCFSymbolStyle +from ducpy.classes.ElementsClass import \ + FeatureControlFrameSegment as DS_FeatureControlFrameSegment +from ducpy.classes.ElementsClass import GeometricPoint as DS_GeometricPoint +from ducpy.classes.ElementsClass import HatchPatternLine as DS_HatchPatternLine +from ducpy.classes.ElementsClass import ImageCrop as DS_ImageCrop +from ducpy.classes.ElementsClass import \ + LeaderBlockContent as DS_LeaderBlockContent +from ducpy.classes.ElementsClass import LeaderContent as DS_LeaderContent +from ducpy.classes.ElementsClass import \ + LeaderTextBlockContent as DS_LeaderTextBlockContent +from ducpy.classes.ElementsClass import LineSpacing as DS_LineSpacing +from ducpy.classes.ElementsClass import \ + ParagraphFormatting as DS_ParagraphFormatting +from ducpy.classes.ElementsClass import ParametricSource as DS_ParametricSource +from ducpy.classes.ElementsClass import PlotLayout as DS_PlotLayout +from ducpy.classes.ElementsClass import \ + PointBindingPoint as DS_PointBindingPoint +from ducpy.classes.ElementsClass import StackFormat as DS_StackFormat +from ducpy.classes.ElementsClass import \ + StackFormatProperties as DS_StackFormatProperties +from ducpy.classes.ElementsClass import StringValueEntry as DS_StringValueEntry +from ducpy.classes.ElementsClass import StrokeSides as DS_StrokeSides +from ducpy.classes.ElementsClass import StrokeStyle as DS_StrokeStyle +from ducpy.classes.ElementsClass import TextColumn as DS_TextColumn +from ducpy.classes.ElementsClass import TilingProperties as DS_TilingProperties +from ducpy.classes.ElementsClass import ToleranceClause as DS_ToleranceClause +from ducpy.classes.StandardsClass import AlternateUnits as DS_AlternateUnits +from ducpy.classes.StandardsClass import \ + AngularUnitSystem as DS_AngularUnitSystem +from ducpy.classes.StandardsClass import \ + DimensionValidationRules as DS_DimensionValidationRules +from ducpy.classes.StandardsClass import \ + DynamicSnapSettings as DS_DynamicSnapSettings +from ducpy.classes.StandardsClass import GridSettings as DS_GridSettings +from ducpy.classes.StandardsClass import GridStyle as DS_GridStyle +from ducpy.classes.StandardsClass import \ + IdentifiedCommonStyle as DS_IdentifiedCommonStyle +from ducpy.classes.StandardsClass import \ + IdentifiedDimensionStyle as DS_IdentifiedDimensionStyle +from ducpy.classes.StandardsClass import \ + IdentifiedDocStyle as DS_IdentifiedDocStyle +from ducpy.classes.StandardsClass import \ + IdentifiedFCFStyle as DS_IdentifiedFCFStyle +from ducpy.classes.StandardsClass import \ + IdentifiedGridSettings as DS_IdentifiedGridSettings +from ducpy.classes.StandardsClass import \ + IdentifiedHatchStyle as DS_IdentifiedHatchStyle +from ducpy.classes.StandardsClass import \ + IdentifiedLeaderStyle as DS_IdentifiedLeaderStyle +from ducpy.classes.StandardsClass import \ + IdentifiedSnapSettings as DS_IdentifiedSnapSettings +from ducpy.classes.StandardsClass import \ + IdentifiedStackLikeStyle as DS_IdentifiedStackLikeStyle +from ducpy.classes.StandardsClass import \ + IdentifiedTableStyle as DS_IdentifiedTableStyle +from ducpy.classes.StandardsClass import \ + IdentifiedTextStyle as DS_IdentifiedTextStyle +from ducpy.classes.StandardsClass import IdentifiedUcs as DS_IdentifiedUcs +from ducpy.classes.StandardsClass import IdentifiedView as DS_IdentifiedView +from ducpy.classes.StandardsClass import \ + IdentifiedViewportStyle as DS_IdentifiedViewportStyle +from ducpy.classes.StandardsClass import \ + IdentifiedXRayStyle as DS_IdentifiedXRayStyle +from ducpy.classes.StandardsClass import Identifier as DS_Identifier +from ducpy.classes.StandardsClass import \ + IsometricGridSettings as DS_IsometricGridSettings +from ducpy.classes.StandardsClass import \ + LayerSnapFilters as DS_LayerSnapFilters +from ducpy.classes.StandardsClass import \ + LayerValidationRules as DS_LayerValidationRules +from ducpy.classes.StandardsClass import \ + LinearUnitSystem as DS_LinearUnitSystem +from ducpy.classes.StandardsClass import \ + PolarGridSettings as DS_PolarGridSettings +from ducpy.classes.StandardsClass import \ + PolarTrackingSettings as DS_PolarTrackingSettings +from ducpy.classes.StandardsClass import PrimaryUnits as DS_PrimaryUnits +from ducpy.classes.StandardsClass import \ + SnapMarkerSettings as DS_SnapMarkerSettings +from ducpy.classes.StandardsClass import SnapMarkerStyle as DS_SnapMarkerStyle +from ducpy.classes.StandardsClass import \ + SnapMarkerStyleEntry as DS_SnapMarkerStyleEntry +from ducpy.classes.StandardsClass import SnapOverride as DS_SnapOverride +from ducpy.classes.StandardsClass import SnapSettings as DS_SnapSettings +from ducpy.classes.StandardsClass import Standard as DS_Standard +from ducpy.classes.StandardsClass import \ + StandardOverrides as DS_StandardOverrides +from ducpy.classes.StandardsClass import StandardStyles as DS_StandardStyles +from ducpy.classes.StandardsClass import StandardUnits as DS_StandardUnits +from ducpy.classes.StandardsClass import \ + StandardValidation as DS_StandardValidation +from ducpy.classes.StandardsClass import \ + StandardViewSettings as DS_StandardViewSettings +from ducpy.classes.StandardsClass import \ + TrackingLineStyle as DS_TrackingLineStyle +from ducpy.classes.StandardsClass import UnitPrecision as DS_UnitPrecision +from ducpy.classes.StandardsClass import UnitSystemBase as DS_UnitSystemBase +# Unions for runtime decisions +from ducpy.Duc import DucTextDynamicSourceData as FBS_DucTextDynamicSourceData +from ducpy.Duc import Element as FBS_Element +from ducpy.Duc import LeaderContentData as FBS_LeaderContentData +from ducpy.Duc._DucElementBase import _DucElementBase as FBSDucElementBase +from ducpy.Duc._DucElementStylesBase import \ + _DucElementStylesBase as FBSDucElementStylesBase +from ducpy.Duc._DucLinearElementBase import \ + _DucLinearElementBase as FBSDucLinearElementBase +from ducpy.Duc._DucStackBase import _DucStackBase as FBSDucStackBase +from ducpy.Duc._DucStackBase import _DucStackBase as FBSDucStackElementBase +from ducpy.Duc._UnitSystemBase import _UnitSystemBase as FBS_UnitSystemBase +from ducpy.Duc.AlternateUnits import AlternateUnits as FBSAlternateUnits +from ducpy.Duc.ANGULAR_UNITS_FORMAT import ANGULAR_UNITS_FORMAT +from ducpy.Duc.AngularUnitSystem import \ + AngularUnitSystem as FBSAngularUnitSystem +from ducpy.Duc.AXIS import AXIS from ducpy.Duc.BEZIER_MIRRORING import BEZIER_MIRRORING -from ducpy.Duc.IMAGE_STATUS import IMAGE_STATUS -from ducpy.Duc.TABLE_CELL_ALIGNMENT import TABLE_CELL_ALIGNMENT -from ducpy.Duc.TABLE_FLOW_DIRECTION import TABLE_FLOW_DIRECTION -from ducpy.Duc.VIEWPORT_SHADE_PLOT import VIEWPORT_SHADE_PLOT -from ducpy.Duc.HATCH_STYLE import HATCH_STYLE +from ducpy.Duc.BLENDING import BLENDING from ducpy.Duc.BLOCK_ATTACHMENT import BLOCK_ATTACHMENT -from ducpy.Duc.TOLERANCE_DISPLAY import TOLERANCE_DISPLAY +from ducpy.Duc.BOOLEAN_OPERATION import BOOLEAN_OPERATION +from ducpy.Duc.BoundElement import BoundElement as FBSBoundElement +from ducpy.Duc.Checkpoint import Checkpoint as FBSCheckpoint +from ducpy.Duc.COLUMN_TYPE import COLUMN_TYPE +from ducpy.Duc.ColumnLayout import ColumnLayout as FBSColumnLayout +from ducpy.Duc.CustomHatchPattern import \ + CustomHatchPattern as FBSCustomHatchPattern +from ducpy.Duc.DatumReference import DatumReference as FBSDatumReference +from ducpy.Duc.DECIMAL_SEPARATOR import DECIMAL_SEPARATOR +from ducpy.Duc.Delta import Delta as FBSDelta from ducpy.Duc.DIMENSION_FIT_RULE import DIMENSION_FIT_RULE from ducpy.Duc.DIMENSION_TEXT_PLACEMENT import DIMENSION_TEXT_PLACEMENT -from ducpy.Duc.MARK_ELLIPSE_CENTER import MARK_ELLIPSE_CENTER from ducpy.Duc.DIMENSION_TYPE import DIMENSION_TYPE -from ducpy.Duc.AXIS import AXIS -from ducpy.Duc.GDT_SYMBOL import GDT_SYMBOL -from ducpy.Duc.MATERIAL_CONDITION import MATERIAL_CONDITION -from ducpy.Duc.TOLERANCE_ZONE_TYPE import TOLERANCE_ZONE_TYPE -from ducpy.Duc.COLUMN_TYPE import COLUMN_TYPE -from ducpy.Duc.TEXT_FLOW_DIRECTION import TEXT_FLOW_DIRECTION -from ducpy.Duc.PARAMETRIC_SOURCE_TYPE import PARAMETRIC_SOURCE_TYPE -from ducpy.Duc.LEADER_CONTENT_TYPE import LEADER_CONTENT_TYPE -from ducpy.Duc.BOOLEAN_OPERATION import BOOLEAN_OPERATION -from ducpy.Duc.UNIT_SYSTEM import UNIT_SYSTEM from ducpy.Duc.DIMENSION_UNITS_FORMAT import DIMENSION_UNITS_FORMAT -from ducpy.Duc.ANGULAR_UNITS_FORMAT import ANGULAR_UNITS_FORMAT -from ducpy.Duc.DECIMAL_SEPARATOR import DECIMAL_SEPARATOR -from ducpy.Duc.GRID_TYPE import GRID_TYPE -from ducpy.Duc.GRID_DISPLAY_TYPE import GRID_DISPLAY_TYPE -from ducpy.Duc.OBJECT_SNAP_MODE import OBJECT_SNAP_MODE -from ducpy.Duc.SNAP_MODE import SNAP_MODE -from ducpy.Duc.SNAP_OVERRIDE_BEHAVIOR import SNAP_OVERRIDE_BEHAVIOR -from ducpy.Duc.SNAP_MARKER_SHAPE import SNAP_MARKER_SHAPE -from ducpy.Duc.PRUNING_LEVEL import PRUNING_LEVEL - -# FlatBuffers generated classes (reading API) -from ducpy.Duc.ExportedDataState import ExportedDataState as FBSExportedDataState -from ducpy.Duc.ElementWrapper import ElementWrapper as FBSElementWrapper -from ducpy.Duc.DucRectangleElement import DucRectangleElement as FBSDucRectangleElement -from ducpy.Duc.DucPolygonElement import DucPolygonElement as FBSDucPolygonElement -from ducpy.Duc.DucEllipseElement import DucEllipseElement as FBSDucEllipseElement -from ducpy.Duc.DucEmbeddableElement import DucEmbeddableElement as FBSDucEmbeddableElement -from ducpy.Duc.DucPdfElement import DucPdfElement as FBSDucPdfElement -from ducpy.Duc.DucMermaidElement import DucMermaidElement as FBSDucMermaidElement -from ducpy.Duc.DucTableElement import DucTableElement as FBSDucTableElement -from ducpy.Duc.DucImageElement import DucImageElement as FBSDucImageElement -from ducpy.Duc.DucTextElement import DucTextElement as FBSDucTextElement -from ducpy.Duc.DucLinearElement import DucLinearElement as FBSDucLinearElement +from ducpy.Duc.DimensionBaselineData import \ + DimensionBaselineData as FBSDimensionBaselineData +from ducpy.Duc.DimensionBindings import \ + DimensionBindings as FBSDimensionBindings +from ducpy.Duc.DimensionContinueData import \ + DimensionContinueData as FBSDimensionContinueData +from ducpy.Duc.DimensionDefinitionPoints import \ + DimensionDefinitionPoints as FBSDimensionDefinitionPoints +from ducpy.Duc.DimensionExtLineStyle import \ + DimensionExtLineStyle as FBSDimensionExtLineStyle +from ducpy.Duc.DimensionFitStyle import \ + DimensionFitStyle as FBSDimensionFitStyle +from ducpy.Duc.DimensionLineStyle import \ + DimensionLineStyle as FBSDimensionLineStyle +from ducpy.Duc.DimensionSymbolStyle import \ + DimensionSymbolStyle as FBSDimensionSymbolStyle +from ducpy.Duc.DimensionToleranceStyle import \ + DimensionToleranceStyle as FBSDimensionToleranceStyle +from ducpy.Duc.DimensionValidationRules import \ + DimensionValidationRules as FBSDimensionValidationRules +from ducpy.Duc.DocumentGridConfig import \ + DocumentGridConfig as FBSDocumentGridConfig from ducpy.Duc.DucArrowElement import DucArrowElement as FBSDucArrowElement -from ducpy.Duc.DucFreeDrawElement import DucFreeDrawElement as FBSDucFreeDrawElement -from ducpy.Duc.DucBlockInstanceElement import DucBlockInstanceElement as FBSDucBlockInstanceElement -from ducpy.Duc.DucFrameElement import DucFrameElement as FBSDucFrameElement -from ducpy.Duc.DucPlotElement import DucPlotElement as FBSDucPlotElement -from ducpy.Duc.DucViewportElement import DucViewportElement as FBSDucViewportElement -from ducpy.Duc.DucXRayElement import DucXRayElement as FBSDucXRayElement -from ducpy.Duc.DucLeaderElement import DucLeaderElement as FBSDucLeaderElement -from ducpy.Duc.DucDimensionElement import DucDimensionElement as FBSDucDimensionElement -from ducpy.Duc.DucFeatureControlFrameElement import DucFeatureControlFrameElement as FBSDucFeatureControlFrameElement +from ducpy.Duc.DucBlock import DucBlock as FBSDucBlock +from ducpy.Duc.DucBlockAttributeDefinition import \ + DucBlockAttributeDefinition as FBSDucBlockAttributeDefinition +from ducpy.Duc.DucBlockAttributeDefinitionEntry import \ + DucBlockAttributeDefinitionEntry as FBSDucBlockAttributeDefinitionEntry +from ducpy.Duc.DucBlockCollection import \ + DucBlockCollection as FBSDucBlockCollection +from ducpy.Duc.DucBlockCollectionEntry import \ + DucBlockCollectionEntry as FBSDucBlockCollectionEntry +from ducpy.Duc.DucBlockDuplicationArray import \ + DucBlockDuplicationArray as FBSDucBlockDuplicationArray +from ducpy.Duc.DucBlockInstanceElement import \ + DucBlockInstanceElement as FBSDucBlockInstanceElement +from ducpy.Duc.DucBlockMetadata import DucBlockMetadata as FBSDucBlockMetadata +from ducpy.Duc.DucCommonStyle import DucCommonStyle as FBSDucCommonStyle +from ducpy.Duc.DucDimensionElement import \ + DucDimensionElement as FBSDucDimensionElement +from ducpy.Duc.DucDimensionStyle import \ + DucDimensionStyle as FBSDucDimensionStyle from ducpy.Duc.DucDocElement import DucDocElement as FBSDucDocElement -from ducpy.Duc.DucParametricElement import DucParametricElement as FBSDucParametricElement -from ducpy.Duc.DucModelElement import DucModelElement as FBSDucModelElement -from ducpy.Duc.DocumentGridConfig import DocumentGridConfig as FBSDocumentGridConfig - -from ducpy.Duc.ElementContentBase import ElementContentBase as FBSElementContentBase -from ducpy.Duc.ElementStroke import ElementStroke as FBSElementStroke -from ducpy.Duc.ElementBackground import ElementBackground as FBSElementBackground -from ducpy.Duc.StrokeStyle import StrokeStyle as FBSStrokeStyle -from ducpy.Duc.StrokeSides import StrokeSides as FBSStrokeSides -from ducpy.Duc._DucElementStylesBase import _DucElementStylesBase as FBSDucElementStylesBase -from ducpy.Duc._DucElementBase import _DucElementBase as FBSDucElementBase -from ducpy.Duc.BoundElement import BoundElement as FBSBoundElement -from ducpy.Duc.DucPoint import DucPoint as FBSDucPoint +from ducpy.Duc.DucDocStyle import DucDocStyle as FBSDucDocStyle +from ducpy.Duc.DucEllipseElement import \ + DucEllipseElement as FBSDucEllipseElement +from ducpy.Duc.DucEmbeddableElement import \ + DucEmbeddableElement as FBSDucEmbeddableElement +# Version graph and external files +from ducpy.Duc.DucExternalFileData import \ + DucExternalFileData as FBSDucExternalFileData +from ducpy.Duc.DucExternalFileEntry import \ + DucExternalFileEntry as FBSDucExternalFileEntry +from ducpy.Duc.DucFeatureControlFrameElement import \ + DucFeatureControlFrameElement as FBSDucFeatureControlFrameElement +from ducpy.Duc.DucFeatureControlFrameStyle import \ + DucFeatureControlFrameStyle as FBSDucFeatureControlFrameStyle +from ducpy.Duc.DucFrameElement import DucFrameElement as FBSDucFrameElement +from ducpy.Duc.DucFreeDrawElement import \ + DucFreeDrawElement as FBSDucFreeDrawElement +from ducpy.Duc.DucFreeDrawEnds import DucFreeDrawEnds as FBSDucFreeDrawEnds +from ducpy.Duc.DucGroup import DucGroup as FBSDucGroup +from ducpy.Duc.DucHatchStyle import DucHatchStyle as FBSDucHatchStyle from ducpy.Duc.DucHead import DucHead as FBSDucHead -from ducpy.Duc.PointBindingPoint import PointBindingPoint as FBSPointBindingPoint -from ducpy.Duc.DucPointBinding import DucPointBinding as FBSDucPointBinding +from ducpy.Duc.DucImageElement import DucImageElement as FBSDucImageElement +from ducpy.Duc.DucImageFilter import DucImageFilter as FBSDucImageFilter +from ducpy.Duc.DucLayer import DucLayer as FBSDucLayer +from ducpy.Duc.DucLayerOverrides import \ + DucLayerOverrides as FBSDucLayerOverrides +from ducpy.Duc.DucLeaderElement import DucLeaderElement as FBSDucLeaderElement +from ducpy.Duc.DucLeaderStyle import DucLeaderStyle as FBSDucLeaderStyle from ducpy.Duc.DucLine import DucLine as FBSDucLine +from ducpy.Duc.DucLinearElement import DucLinearElement as FBSDucLinearElement from ducpy.Duc.DucLineReference import DucLineReference as FBSDucLineReference +from ducpy.Duc.DucMermaidElement import \ + DucMermaidElement as FBSDucMermaidElement +from ducpy.Duc.DucModelElement import DucModelElement as FBSDucModelElement +from ducpy.Duc.DucParametricElement import \ + DucParametricElement as FBSDucParametricElement from ducpy.Duc.DucPath import DucPath as FBSDucPath -from ducpy.Duc._DucLinearElementBase import _DucLinearElementBase as FBSDucLinearElementBase -from ducpy.Duc._DucStackBase import _DucStackBase as FBSDucStackBase -from ducpy.Duc.DucTextStyle import DucTextStyle as FBSDucTextStyle -from ducpy.Duc.LineSpacing import LineSpacing as FBSLineSpacing -from ducpy.Duc.Margins import Margins as FBSMargins - -from ducpy.Duc.DucTableStyle import DucTableStyle as FBSDucTableStyle -from ducpy.Duc.DucTableCellStyle import DucTableCellStyle as FBSDucTableCellStyle -from ducpy.Duc.DucTableColumn import DucTableColumn as FBSDucTableColumn -from ducpy.Duc.DucTableRow import DucTableRow as FBSDucTableRow +from ducpy.Duc.DucPdfElement import DucPdfElement as FBSDucPdfElement +from ducpy.Duc.DucPlotElement import DucPlotElement as FBSDucPlotElement +from ducpy.Duc.DucPlotStyle import DucPlotStyle as FBSDucPlotStyle +from ducpy.Duc.DucPoint import DucPoint as FBSDucPoint +from ducpy.Duc.DucPointBinding import DucPointBinding as FBSDucPointBinding +from ducpy.Duc.DucPolygonElement import \ + DucPolygonElement as FBSDucPolygonElement +from ducpy.Duc.DucRectangleElement import \ + DucRectangleElement as FBSDucRectangleElement +from ducpy.Duc.DucRegion import DucRegion as FBSDucRegion +from ducpy.Duc.DucTableAutoSize import DucTableAutoSize as FBSDucTableAutoSize from ducpy.Duc.DucTableCell import DucTableCell as FBSDucTableCell +from ducpy.Duc.DucTableCellEntry import \ + DucTableCellEntry as FBSDucTableCellEntry from ducpy.Duc.DucTableCellSpan import DucTableCellSpan as FBSDucTableCellSpan -from ducpy.Duc.DucTableColumnEntry import DucTableColumnEntry as FBSDucTableColumnEntry +from ducpy.Duc.DucTableCellStyle import \ + DucTableCellStyle as FBSDucTableCellStyle +from ducpy.Duc.DucTableColumn import DucTableColumn as FBSDucTableColumn +from ducpy.Duc.DucTableColumnEntry import \ + DucTableColumnEntry as FBSDucTableColumnEntry +from ducpy.Duc.DucTableElement import DucTableElement as FBSDucTableElement +from ducpy.Duc.DucTableRow import DucTableRow as FBSDucTableRow from ducpy.Duc.DucTableRowEntry import DucTableRowEntry as FBSDucTableRowEntry -from ducpy.Duc.DucTableCellEntry import DucTableCellEntry as FBSDucTableCellEntry -from ducpy.Duc.DucTableAutoSize import DucTableAutoSize as FBSDucTableAutoSize - -from ducpy.Duc.ImageCrop import ImageCrop as FBSImageCrop -from ducpy.Duc.DucImageFilter import DucImageFilter as FBSDucImageFilter -from ducpy.Duc.TilingProperties import TilingProperties as FBSTilingProperties -from ducpy.Duc.HatchPatternLine import HatchPatternLine as FBSHatchPatternLine -from ducpy.Duc.CustomHatchPattern import CustomHatchPattern as FBSCustomHatchPattern -from ducpy.Duc.DucHatchStyle import DucHatchStyle as FBSDucHatchStyle - -from ducpy.Duc.DucTextDynamicElementSource import DucTextDynamicElementSource as FBSDucTextDynamicElementSource -from ducpy.Duc.DucTextDynamicDictionarySource import DucTextDynamicDictionarySource as FBSDucTextDynamicDictionarySource -from ducpy.Duc.DucTextDynamicSource import DucTextDynamicSource as FBSDucTextDynamicSource -from ducpy.Duc.DucTextDynamicPart import DucTextDynamicPart as FBSDucTextDynamicPart - -from ducpy.Duc.DucFreeDrawEnds import DucFreeDrawEnds as FBSDucFreeDrawEnds - -from ducpy.Duc.StringValueEntry import StringValueEntry as FBSStringValueEntry -from ducpy.Duc.DucBlockDuplicationArray import DucBlockDuplicationArray as FBSDucBlockDuplicationArray - -from ducpy.Duc.PlotLayout import PlotLayout as FBSPlotLayout -from ducpy.Duc.DucPlotStyle import DucPlotStyle as FBSDucPlotStyle +from ducpy.Duc.DucTableStyle import DucTableStyle as FBSDucTableStyle +from ducpy.Duc.DucTextDynamicDictionarySource import \ + DucTextDynamicDictionarySource as FBSDucTextDynamicDictionarySource +from ducpy.Duc.DucTextDynamicElementSource import \ + DucTextDynamicElementSource as FBSDucTextDynamicElementSource +from ducpy.Duc.DucTextDynamicPart import \ + DucTextDynamicPart as FBSDucTextDynamicPart +from ducpy.Duc.DucTextDynamicSource import \ + DucTextDynamicSource as FBSDucTextDynamicSource +from ducpy.Duc.DucTextElement import DucTextElement as FBSDucTextElement +from ducpy.Duc.DucTextStyle import DucTextStyle as FBSDucTextStyle +from ducpy.Duc.DucUcs import DucUcs as FBSDucUcs +from ducpy.Duc.DucView import DucView as FBSDucView +from ducpy.Duc.DucViewportElement import \ + DucViewportElement as FBSDucViewportElement from ducpy.Duc.DucViewportStyle import DucViewportStyle as FBSDucViewportStyle +from ducpy.Duc.DucXRayElement import DucXRayElement as FBSDucXRayElement from ducpy.Duc.DucXRayStyle import DucXRayStyle as FBSDucXRayStyle - -from ducpy.Duc.LeaderTextBlockContent import LeaderTextBlockContent as FBSLeaderTextBlockContent -from ducpy.Duc.LeaderBlockContent import LeaderBlockContent as FBSLeaderBlockContent -from ducpy.Duc.LeaderContent import LeaderContent as FBSLeaderContent - -from ducpy.Duc.DimensionDefinitionPoints import DimensionDefinitionPoints as FBSDimensionDefinitionPoints -from ducpy.Duc.DimensionBindings import DimensionBindings as FBSDimensionBindings -from ducpy.Duc.DimensionBaselineData import DimensionBaselineData as FBSDimensionBaselineData -from ducpy.Duc.DimensionContinueData import DimensionContinueData as FBSDimensionContinueData - -from ducpy.Duc.DimensionLineStyle import DimensionLineStyle as FBSDimensionLineStyle -from ducpy.Duc.DimensionExtLineStyle import DimensionExtLineStyle as FBSDimensionExtLineStyle -from ducpy.Duc.DimensionSymbolStyle import DimensionSymbolStyle as FBSDimensionSymbolStyle -from ducpy.Duc.DimensionToleranceStyle import DimensionToleranceStyle as FBSDimensionToleranceStyle -from ducpy.Duc.DucDimensionStyle import DucDimensionStyle as FBSDucDimensionStyle - -from ducpy.Duc.DatumReference import DatumReference as FBSDatumReference -from ducpy.Duc.ToleranceClause import ToleranceClause as FBSToleranceClause -from ducpy.Duc.FeatureControlFrameSegment import FeatureControlFrameSegment as FBSFeatureControlFrameSegment -from ducpy.Duc.FCFBetweenModifier import FCFBetweenModifier as FBSFCFBetweenModifier -from ducpy.Duc.FCFProjectedZoneModifier import FCFProjectedZoneModifier as FBSFCFProjectedZoneModifier -from ducpy.Duc.FCFFrameModifiers import FCFFrameModifiers as FBSFCFFrameModifiers -from ducpy.Duc.FCFDatumDefinition import FCFDatumDefinition as FBSFCFDatumDefinition -from ducpy.Duc.FCFSegmentRow import FCFSegmentRow as FBSFCFSegmentRow -from ducpy.Duc.DucFeatureControlFrameStyle import DucFeatureControlFrameStyle as FBSDucFeatureControlFrameStyle +from ducpy.Duc.DynamicSnapSettings import \ + DynamicSnapSettings as FBSDynamicSnapSettings +from ducpy.Duc.ELEMENT_CONTENT_PREFERENCE import ELEMENT_CONTENT_PREFERENCE +from ducpy.Duc.ElementBackground import \ + ElementBackground as FBSElementBackground +from ducpy.Duc.ElementContentBase import \ + ElementContentBase as FBSElementContentBase +from ducpy.Duc.ElementStroke import ElementStroke as FBSElementStroke +from ducpy.Duc.ElementWrapper import ElementWrapper as FBSElementWrapper +# FlatBuffers generated classes (reading API) +from ducpy.Duc.ExportedDataState import \ + ExportedDataState as FBSExportedDataState +from ducpy.Duc.FCFBetweenModifier import \ + FCFBetweenModifier as FBSFCFBetweenModifier +from ducpy.Duc.FCFDatumDefinition import \ + FCFDatumDefinition as FBSFCFDatumDefinition +from ducpy.Duc.FCFDatumStyle import FCFDatumStyle as FBSFCFDatumStyle +from ducpy.Duc.FCFFrameModifiers import \ + FCFFrameModifiers as FBSFCFFrameModifiers from ducpy.Duc.FCFLayoutStyle import FCFLayoutStyle as FBSFCFLayoutStyle +from ducpy.Duc.FCFProjectedZoneModifier import \ + FCFProjectedZoneModifier as FBSFCFProjectedZoneModifier +from ducpy.Duc.FCFSegmentRow import FCFSegmentRow as FBSFCFSegmentRow from ducpy.Duc.FCFSymbolStyle import FCFSymbolStyle as FBSFCFSymbolStyle -from ducpy.Duc.FCFDatumStyle import FCFDatumStyle as FBSFCFDatumStyle - -from ducpy.Duc.TextColumn import TextColumn as FBSTextColumn -from ducpy.Duc.ColumnLayout import ColumnLayout as FBSColumnLayout -from ducpy.Duc.DucDocStyle import DucDocStyle as FBSDucDocStyle - -from ducpy.Duc.ParametricSource import ParametricSource as FBSParametricSource - -from ducpy.Duc.DucBlock import DucBlock as FBSDucBlock -from ducpy.Duc.DucBlockCollection import DucBlockCollection as FBSDucBlockCollection -from ducpy.Duc.DucBlockCollectionEntry import DucBlockCollectionEntry as FBSDucBlockCollectionEntry -from ducpy.Duc.DucBlockMetadata import DucBlockMetadata as FBSDucBlockMetadata -from ducpy.Duc.DucBlockAttributeDefinition import DucBlockAttributeDefinition as FBSDucBlockAttributeDefinition -from ducpy.Duc.DucBlockAttributeDefinitionEntry import DucBlockAttributeDefinitionEntry as FBSDucBlockAttributeDefinitionEntry - -from ducpy.Duc.DucGroup import DucGroup as FBSDucGroup -from ducpy.Duc.DucRegion import DucRegion as FBSDucRegion -from ducpy.Duc.DucLayer import DucLayer as FBSDucLayer -from ducpy.Duc.DucLayerOverrides import DucLayerOverrides as FBSDucLayerOverrides - +from ducpy.Duc.FeatureControlFrameSegment import \ + FeatureControlFrameSegment as FBSFeatureControlFrameSegment +from ducpy.Duc.GDT_SYMBOL import GDT_SYMBOL +from ducpy.Duc.GRID_DISPLAY_TYPE import GRID_DISPLAY_TYPE +from ducpy.Duc.GRID_TYPE import GRID_TYPE +from ducpy.Duc.GridSettings import GridSettings as FBSGridSettings +from ducpy.Duc.GridStyle import GridStyle as FBSGridStyle +from ducpy.Duc.HATCH_STYLE import HATCH_STYLE +from ducpy.Duc.HatchPatternLine import HatchPatternLine as FBSHatchPatternLine +from ducpy.Duc.IdentifiedCommonStyle import \ + IdentifiedCommonStyle as FBSIdentifiedCommonStyle +from ducpy.Duc.IdentifiedDimensionStyle import \ + IdentifiedDimensionStyle as FBSIdentifiedDimensionStyle +from ducpy.Duc.IdentifiedDocStyle import \ + IdentifiedDocStyle as FBSIdentifiedDocStyle +from ducpy.Duc.IdentifiedFCFStyle import \ + IdentifiedFCFStyle as FBSIdentifiedFCFStyle +from ducpy.Duc.IdentifiedGridSettings import \ + IdentifiedGridSettings as FBSIdentifiedGridSettings +from ducpy.Duc.IdentifiedHatchStyle import \ + IdentifiedHatchStyle as FBSIdentifiedHatchStyle +from ducpy.Duc.IdentifiedLeaderStyle import \ + IdentifiedLeaderStyle as FBSIdentifiedLeaderStyle +from ducpy.Duc.IdentifiedSnapSettings import \ + IdentifiedSnapSettings as FBSIdentifiedSnapSettings +from ducpy.Duc.IdentifiedStackLikeStyle import \ + IdentifiedStackLikeStyle as FBSIdentifiedStackLikeStyle +from ducpy.Duc.IdentifiedTableStyle import \ + IdentifiedTableStyle as FBSIdentifiedTableStyle +from ducpy.Duc.IdentifiedTextStyle import \ + IdentifiedTextStyle as FBSIdentifiedTextStyle +from ducpy.Duc.IdentifiedUcs import IdentifiedUcs as FBSIdentifiedUcs +from ducpy.Duc.IdentifiedView import IdentifiedView as FBSIdentifiedView +from ducpy.Duc.IdentifiedViewportStyle import \ + IdentifiedViewportStyle as FBSIdentifiedViewportStyle +from ducpy.Duc.IdentifiedXRayStyle import \ + IdentifiedXRayStyle as FBSIdentifiedXRayStyle # Standards FB classes from ducpy.Duc.Identifier import Identifier as FBSIdentifier -from ducpy.Duc._UnitSystemBase import _UnitSystemBase as FBS_UnitSystemBase +from ducpy.Duc.IMAGE_STATUS import IMAGE_STATUS +from ducpy.Duc.ImageCrop import ImageCrop as FBSImageCrop +from ducpy.Duc.IsometricGridSettings import \ + IsometricGridSettings as FBSIsometricGridSettings +from ducpy.Duc.JSONPatchOperation import \ + JSONPatchOperation as FBSJSONPatchOperation +from ducpy.Duc.LayerSnapFilters import LayerSnapFilters as FBSLayerSnapFilters +from ducpy.Duc.LayerValidationRules import \ + LayerValidationRules as FBSLayerValidationRules +from ducpy.Duc.LEADER_CONTENT_TYPE import LEADER_CONTENT_TYPE +from ducpy.Duc.LeaderBlockContent import \ + LeaderBlockContent as FBSLeaderBlockContent +from ducpy.Duc.LeaderContent import LeaderContent as FBSLeaderContent +from ducpy.Duc.LeaderTextBlockContent import \ + LeaderTextBlockContent as FBSLeaderTextBlockContent +from ducpy.Duc.LINE_HEAD import LINE_HEAD +from ducpy.Duc.LINE_SPACING_TYPE import LINE_SPACING_TYPE from ducpy.Duc.LinearUnitSystem import LinearUnitSystem as FBSLinearUnitSystem -from ducpy.Duc.AngularUnitSystem import AngularUnitSystem as FBSAngularUnitSystem -from ducpy.Duc.AlternateUnits import AlternateUnits as FBSAlternateUnits +from ducpy.Duc.LineSpacing import LineSpacing as FBSLineSpacing +from ducpy.Duc.Margins import Margins as FBSMargins +from ducpy.Duc.MARK_ELLIPSE_CENTER import MARK_ELLIPSE_CENTER +from ducpy.Duc.MATERIAL_CONDITION import MATERIAL_CONDITION +from ducpy.Duc.OBJECT_SNAP_MODE import OBJECT_SNAP_MODE +from ducpy.Duc.PARAMETRIC_SOURCE_TYPE import PARAMETRIC_SOURCE_TYPE +from ducpy.Duc.ParametricSource import ParametricSource as FBSParametricSource +from ducpy.Duc.PlotLayout import PlotLayout as FBSPlotLayout +from ducpy.Duc.PointBindingPoint import \ + PointBindingPoint as FBSPointBindingPoint +from ducpy.Duc.PolarGridSettings import \ + PolarGridSettings as FBSPolarGridSettings +from ducpy.Duc.PolarTrackingSettings import \ + PolarTrackingSettings as FBSPolarTrackingSettings from ducpy.Duc.PrimaryUnits import PrimaryUnits as FBSPrimaryUnits -from ducpy.Duc.StandardUnits import StandardUnits as FBSStandardUnits -from ducpy.Duc.UnitPrecision import UnitPrecision as FBSUnitPrecision -from ducpy.Duc.StandardOverrides import StandardOverrides as FBSStandardOverrides - -from ducpy.Duc.DucCommonStyle import DucCommonStyle as FBSDucCommonStyle -from ducpy.Duc.IdentifiedCommonStyle import IdentifiedCommonStyle as FBSIdentifiedCommonStyle -from ducpy.Duc.IdentifiedStackLikeStyle import IdentifiedStackLikeStyle as FBSIdentifiedStackLikeStyle -from ducpy.Duc.IdentifiedTextStyle import IdentifiedTextStyle as FBSIdentifiedTextStyle -from ducpy.Duc.IdentifiedDimensionStyle import IdentifiedDimensionStyle as FBSIdentifiedDimensionStyle -from ducpy.Duc.IdentifiedLeaderStyle import IdentifiedLeaderStyle as FBSIdentifiedLeaderStyle -from ducpy.Duc.IdentifiedFCFStyle import IdentifiedFCFStyle as FBSIdentifiedFCFStyle -from ducpy.Duc.IdentifiedTableStyle import IdentifiedTableStyle as FBSIdentifiedTableStyle -from ducpy.Duc.IdentifiedDocStyle import IdentifiedDocStyle as FBSIdentifiedDocStyle -from ducpy.Duc.IdentifiedViewportStyle import IdentifiedViewportStyle as FBSIdentifiedViewportStyle -from ducpy.Duc.IdentifiedHatchStyle import IdentifiedHatchStyle as FBSIdentifiedHatchStyle -from ducpy.Duc.IdentifiedXRayStyle import IdentifiedXRayStyle as FBSIdentifiedXRayStyle -from ducpy.Duc.StandardStyles import StandardStyles as FBSStandardStyles - -from ducpy.Duc.GridStyle import GridStyle as FBSGridStyle -from ducpy.Duc.PolarGridSettings import PolarGridSettings as FBSPolarGridSettings -from ducpy.Duc.IsometricGridSettings import IsometricGridSettings as FBSIsometricGridSettings -from ducpy.Duc.GridSettings import GridSettings as FBSGridSettings -from ducpy.Duc.SnapOverride import SnapOverride as FBSSnapOverride -from ducpy.Duc.DynamicSnapSettings import DynamicSnapSettings as FBSDynamicSnapSettings -from ducpy.Duc.PolarTrackingSettings import PolarTrackingSettings as FBSPolarTrackingSettings -from ducpy.Duc.TrackingLineStyle import TrackingLineStyle as FBSTrackingLineStyle -from ducpy.Duc.LayerSnapFilters import LayerSnapFilters as FBSLayerSnapFilters +from ducpy.Duc.PRUNING_LEVEL import PRUNING_LEVEL +from ducpy.Duc.SNAP_MARKER_SHAPE import SNAP_MARKER_SHAPE +from ducpy.Duc.SNAP_MODE import SNAP_MODE +from ducpy.Duc.SNAP_OVERRIDE_BEHAVIOR import SNAP_OVERRIDE_BEHAVIOR +from ducpy.Duc.SnapMarkerSettings import \ + SnapMarkerSettings as FBSSnapMarkerSettings from ducpy.Duc.SnapMarkerStyle import SnapMarkerStyle as FBSSnapMarkerStyle -from ducpy.Duc.SnapMarkerStyleEntry import SnapMarkerStyleEntry as FBSSnapMarkerStyleEntry -from ducpy.Duc.SnapMarkerSettings import SnapMarkerSettings as FBSSnapMarkerSettings +from ducpy.Duc.SnapMarkerStyleEntry import \ + SnapMarkerStyleEntry as FBSSnapMarkerStyleEntry +from ducpy.Duc.SnapOverride import SnapOverride as FBSSnapOverride from ducpy.Duc.SnapSettings import SnapSettings as FBSSnapSettings -from ducpy.Duc.IdentifiedGridSettings import IdentifiedGridSettings as FBSIdentifiedGridSettings -from ducpy.Duc.IdentifiedSnapSettings import IdentifiedSnapSettings as FBSIdentifiedSnapSettings -from ducpy.Duc.IdentifiedUcs import IdentifiedUcs as FBSIdentifiedUcs -from ducpy.Duc.IdentifiedView import IdentifiedView as FBSIdentifiedView -from ducpy.Duc.DucUcs import DucUcs as FBSDucUcs -from ducpy.Duc.DucView import DucView as FBSDucView -from ducpy.Duc.StandardViewSettings import StandardViewSettings as FBSStandardViewSettings -from ducpy.Duc.DimensionValidationRules import DimensionValidationRules as FBSDimensionValidationRules -from ducpy.Duc.LayerValidationRules import LayerValidationRules as FBSLayerValidationRules -from ducpy.Duc.StandardValidation import StandardValidation as FBSStandardValidation from ducpy.Duc.Standard import Standard as FBSStandard -from ducpy.Duc.DucLeaderStyle import DucLeaderStyle as FBSDucLeaderStyle -from ducpy.Duc._DucStackBase import _DucStackBase as FBSDucStackElementBase -from ducpy.Duc.DimensionFitStyle import DimensionFitStyle as FBSDimensionFitStyle - -# Version graph and external files -from ducpy.Duc.DucExternalFileData import DucExternalFileData as FBSDucExternalFileData -from ducpy.Duc.DucExternalFileEntry import DucExternalFileEntry as FBSDucExternalFileEntry - -from ducpy.Duc.VersionBase import VersionBase as FBSVersionBase -from ducpy.Duc.Checkpoint import Checkpoint as FBSCheckpoint -from ducpy.Duc.JSONPatchOperation import JSONPatchOperation as FBSJSONPatchOperation -from ducpy.Duc.Delta import Delta as FBSDelta -from ducpy.Duc.VersionGraphMetadata import VersionGraphMetadata as FBSVersionGraphMetadata -from ducpy.Duc.VersionGraph import VersionGraph as FBSVersionGraph - -# Unions for runtime decisions -from ducpy.Duc import Element as FBS_Element -from ducpy.Duc import LeaderContentData as FBS_LeaderContentData -from ducpy.Duc import DucTextDynamicSourceData as FBS_DucTextDynamicSourceData - +from ducpy.Duc.StandardOverrides import \ + StandardOverrides as FBSStandardOverrides +from ducpy.Duc.StandardStyles import StandardStyles as FBSStandardStyles +from ducpy.Duc.StandardUnits import StandardUnits as FBSStandardUnits +from ducpy.Duc.StandardValidation import \ + StandardValidation as FBSStandardValidation +from ducpy.Duc.StandardViewSettings import \ + StandardViewSettings as FBSStandardViewSettings +from ducpy.Duc.StringValueEntry import StringValueEntry as FBSStringValueEntry +from ducpy.Duc.STROKE_CAP import STROKE_CAP +from ducpy.Duc.STROKE_JOIN import STROKE_JOIN +from ducpy.Duc.STROKE_PLACEMENT import STROKE_PLACEMENT +from ducpy.Duc.STROKE_PREFERENCE import STROKE_PREFERENCE +from ducpy.Duc.STROKE_SIDE_PREFERENCE import STROKE_SIDE_PREFERENCE +from ducpy.Duc.StrokeSides import StrokeSides as FBSStrokeSides +from ducpy.Duc.StrokeStyle import StrokeStyle as FBSStrokeStyle +from ducpy.Duc.TABLE_CELL_ALIGNMENT import TABLE_CELL_ALIGNMENT +from ducpy.Duc.TABLE_FLOW_DIRECTION import TABLE_FLOW_DIRECTION +# Enums (we only need types for Optional typing and constants occasionally) +from ducpy.Duc.TEXT_ALIGN import TEXT_ALIGN # Import missing enums from ducpy.Duc.TEXT_FIELD_SOURCE_TYPE import TEXT_FIELD_SOURCE_TYPE - +from ducpy.Duc.TEXT_FLOW_DIRECTION import TEXT_FLOW_DIRECTION +from ducpy.Duc.TextColumn import TextColumn as FBSTextColumn +from ducpy.Duc.TilingProperties import TilingProperties as FBSTilingProperties +from ducpy.Duc.TOLERANCE_DISPLAY import TOLERANCE_DISPLAY +from ducpy.Duc.TOLERANCE_ZONE_TYPE import TOLERANCE_ZONE_TYPE +from ducpy.Duc.ToleranceClause import ToleranceClause as FBSToleranceClause +from ducpy.Duc.TrackingLineStyle import \ + TrackingLineStyle as FBSTrackingLineStyle +from ducpy.Duc.UNIT_SYSTEM import UNIT_SYSTEM +from ducpy.Duc.UnitPrecision import UnitPrecision as FBSUnitPrecision +from ducpy.Duc.VersionBase import VersionBase as FBSVersionBase +from ducpy.Duc.VersionGraph import VersionGraph as FBSVersionGraph +from ducpy.Duc.VersionGraphMetadata import \ + VersionGraphMetadata as FBSVersionGraphMetadata +from ducpy.Duc.VERTICAL_ALIGN import VERTICAL_ALIGN +from ducpy.Duc.VIEWPORT_SHADE_PLOT import VIEWPORT_SHADE_PLOT +from flatbuffers.table import Table # ============================================================================= # Helpers @@ -557,7 +691,8 @@ def parse_fbs_duc_point(obj: FBSDucPoint) -> DS_DucPoint: def parse_fbs_margins(obj: FBSMargins): # This function returns DS_Margins equivalent; but DS dataclasses embed this inside their own types. # We'll return a simple structure for margins where used (DucTableCellStyle.margins, PlotLayout.margins). - from ducpy.classes.ElementsClass import Margins as DS_Margins # local import to avoid name shadowing + from ducpy.classes.ElementsClass import \ + Margins as DS_Margins # local import to avoid name shadowing return DS_Margins( top=obj.Top(), right=obj.Right(), @@ -580,7 +715,8 @@ def parse_fbs_tiling_properties(obj: FBSTilingProperties) -> Optional[DS_TilingP ) def parse_fbs_hatch_pattern_line(obj: FBSHatchPatternLine): - from ducpy.classes.ElementsClass import HatchPatternLine as DS_HatchPatternLine + from ducpy.classes.ElementsClass import \ + HatchPatternLine as DS_HatchPatternLine origin = parse_fbs_duc_point(obj.Origin()) offset = _read_double_vector(obj, "OffsetLength", "Offset") dash = _read_double_vector(obj, "DashPatternLength", "DashPattern") @@ -594,7 +730,8 @@ def parse_fbs_hatch_pattern_line(obj: FBSHatchPatternLine): def parse_fbs_custom_hatch_pattern(obj: FBSCustomHatchPattern): if obj is None: return None - from ducpy.classes.ElementsClass import CustomHatchPattern as DS_CustomHatchPattern + from ducpy.classes.ElementsClass import \ + CustomHatchPattern as DS_CustomHatchPattern lines = [parse_fbs_hatch_pattern_line(obj.Lines(i)) for i in range(obj.LinesLength())] return DS_CustomHatchPattern( name=_s_req(obj.Name()), @@ -1104,9 +1241,7 @@ def parse_fbs_document_grid_config(obj: FBSDocumentGridConfig) -> DS_DocumentGri def parse_fbs_pdf(obj: FBSDucPdfElement) -> DS_DucPdfElement: grid_config = obj.GridConfig() if grid_config: - grid_config_obj = FBSDocumentGridConfig() - grid_config_obj.Init(obj.TableBytes, grid_config) - parsed_grid_config = parse_fbs_document_grid_config(grid_config_obj) + parsed_grid_config = parse_fbs_document_grid_config(grid_config) else: parsed_grid_config = DS_DocumentGridConfig( columns=1, @@ -1253,7 +1388,9 @@ def parse_fbs_primary_units(obj: FBSPrimaryUnits) -> DS_PrimaryUnits: from ducpy.classes.StandardsClass import PrimaryUnits as DS_PrimaryUnits linear = None if obj.Linear(): - from ducpy.Duc.LinearUnitSystem import LinearUnitSystem as FBSLinearUnitSystem + from ducpy.Duc.LinearUnitSystem import \ + LinearUnitSystem as FBSLinearUnitSystem + # full parse done later; here we just set to None to avoid circular. angular = None return DS_PrimaryUnits(linear=linear, angular=angular) @@ -1541,9 +1678,7 @@ def parse_fbs_doc(obj: FBSDucDocElement) -> DS_DucDocElement: dynamics = [parse_fbs_text_dynamic_part(obj.Dynamic(i)) for i in range(obj.DynamicLength())] grid_config = obj.GridConfig() if grid_config: - grid_config_obj = FBSDocumentGridConfig() - grid_config_obj.Init(obj.TableBytes, grid_config) - parsed_grid_config = parse_fbs_document_grid_config(grid_config_obj) + parsed_grid_config = parse_fbs_document_grid_config(grid_config) else: parsed_grid_config = DS_DocumentGridConfig( columns=1, @@ -2012,7 +2147,9 @@ def parse_fbs_standard_styles(obj: FBSStandardStyles) -> Optional[DS_StandardSty # ------------------------------ # bring DS_DucUcs and DS_DucView into scope -from ducpy.classes.ElementsClass import DucUcs as DS_DucUcs, DucView as DS_DucView +from ducpy.classes.ElementsClass import DucUcs as DS_DucUcs +from ducpy.classes.ElementsClass import DucView as DS_DucView + def parse_fbs_duc_ucs(obj: FBSDucUcs) -> DS_DucUcs: origin = obj.Origin() From f6c5b63025d32a04f319a9277c9569fad811a779 Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Sat, 21 Feb 2026 09:30:49 +0000 Subject: [PATCH 07/13] deps: update hipdf version and checksum --- Cargo.lock | 4 +- .../ducjs/src/utils/elements/newElement.ts | 107 ++++++++++-------- .../ducjs/src/utils/elements/textElement.ts | 40 +++++-- 3 files changed, 93 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 726945d..7ca6c26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -592,7 +592,9 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hipdf" -version = "0.0.0-development" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8e0025c137fd7f666ec73ba8a1c8c7f4940edb83fdb77013e5e12703204c80" dependencies = [ "jpeg-decoder", "lopdf", diff --git a/packages/ducjs/src/utils/elements/newElement.ts b/packages/ducjs/src/utils/elements/newElement.ts index f9d9b0f..3b660a2 100644 --- a/packages/ducjs/src/utils/elements/newElement.ts +++ b/packages/ducjs/src/utils/elements/newElement.ts @@ -1,65 +1,64 @@ import { getUpdatedTimestamp, getZoom } from ".."; import { - BLOCK_ATTACHMENT, - COLUMN_TYPE, - DATUM_BRACKET_STYLE, - IMAGE_STATUS, - LINE_SPACING_TYPE, - PARAMETRIC_SOURCE_TYPE, - STACKED_TEXT_ALIGN, - TEXT_FLOW_DIRECTION, - VERTICAL_ALIGN, - VIEWPORT_SHADE_PLOT + BLOCK_ATTACHMENT, + COLUMN_TYPE, + DATUM_BRACKET_STYLE, + IMAGE_STATUS, + LINE_SPACING_TYPE, + STACKED_TEXT_ALIGN, + TEXT_FLOW_DIRECTION, + VERTICAL_ALIGN, + VIEWPORT_SHADE_PLOT } from "../../flatbuffers/duc"; import { getPrecisionValueFromRaw } from "../../technical/scopes"; import { RawValue, Scope } from "../../types"; import { - DucArrowElement, - DucDimensionElement, - DucDocElement, - DucElement, - DucEllipseElement, - DucEmbeddableElement, - DucFeatureControlFrameElement, - DucFrameElement, - DucFreeDrawElement, - DucGenericElement, - DucImageElement, - DucLeaderElement, - DucLinearElement, - DucMermaidElement, - DucModelElement, - DucPdfElement, - DucPlotElement, - DucPolygonElement, - DucTableElement, - DucTextElement, - DucViewportElement, - DucXRayElement, - ElementConstructorOpts, - ElementUpdate, - NonDeleted, - ViewportScale + DucArrowElement, + DucDimensionElement, + DucDocElement, + DucElement, + DucEllipseElement, + DucEmbeddableElement, + DucFeatureControlFrameElement, + DucFrameElement, + DucFreeDrawElement, + DucGenericElement, + DucImageElement, + DucLeaderElement, + DucLinearElement, + DucMermaidElement, + DucModelElement, + DucPdfElement, + DucPlotElement, + DucPolygonElement, + DucTableElement, + DucTextElement, + DucViewportElement, + DucXRayElement, + ElementConstructorOpts, + ElementUpdate, + NonDeleted, + ViewportScale } from "../../types/elements"; import { Radian, ScaleFactor } from "../../types/geometryTypes"; import { Merge, Mutable } from "../../types/utility-types"; import { - DEFAULT_ELEMENT_PROPS, - DEFAULT_ELLIPSE_ELEMENT, - DEFAULT_FONT_FAMILY, - DEFAULT_FONT_SIZE, - DEFAULT_FREEDRAW_ELEMENT, - DEFAULT_POLYGON_SIDES, - DEFAULT_TEXT_ALIGN, - DEFAULT_VERTICAL_ALIGN + DEFAULT_ELEMENT_PROPS, + DEFAULT_ELLIPSE_ELEMENT, + DEFAULT_FONT_FAMILY, + DEFAULT_FONT_SIZE, + DEFAULT_FREEDRAW_ELEMENT, + DEFAULT_POLYGON_SIDES, + DEFAULT_TEXT_ALIGN, + DEFAULT_VERTICAL_ALIGN } from "../constants"; import { randomId, randomInteger } from "../math/random"; import { normalizeText } from "../normalize"; import { getDefaultStackProperties, getDefaultTableData, getDefaultTextStyle } from "./"; import { - getFontString, - getTextElementPositionOffsets, - measureText, + getFontString, + getTextElementPositionOffsets, + measureText, } from "./textElement"; export const newElementWith = ( @@ -309,6 +308,16 @@ export const newTextElement = ( const verticalAlign = opts.verticalAlign || DEFAULT_VERTICAL_ALIGN; const offsets = getTextElementPositionOffsets({ textAlign, verticalAlign }, metrics); + // Minimum dimensions: at least 1px wide, at least one line high (NaN-safe) + const rawMinLineHeight = fontSize.value * lineHeight; + const minLineHeight = (Number.isFinite(rawMinLineHeight) && rawMinLineHeight > 0) + ? rawMinLineHeight + : DEFAULT_FONT_SIZE * lineHeight; + const finalWidth = (Number.isFinite(metrics.width) && metrics.width > 0) ? metrics.width : 1 as RawValue; + const finalHeight = (Number.isFinite(metrics.height) && metrics.height > 0) + ? Math.max(metrics.height, minLineHeight) as RawValue + : minLineHeight as RawValue; + const x = getPrecisionValueFromRaw(opts.x.value - offsets.x as RawValue, scope, currentScope); const y = getPrecisionValueFromRaw(opts.y.value - offsets.y as RawValue, scope, currentScope); @@ -320,8 +329,8 @@ export const newTextElement = ( fontFamily, textAlign, verticalAlign, - width: getPrecisionValueFromRaw(metrics.width, scope, currentScope), - height: getPrecisionValueFromRaw(metrics.height, scope, currentScope), + width: getPrecisionValueFromRaw(finalWidth, scope, currentScope), + height: getPrecisionValueFromRaw(finalHeight, scope, currentScope), containerId: opts.containerId || null, originalText: opts.originalText ?? text, autoResize: opts.autoResize ?? true, diff --git a/packages/ducjs/src/utils/elements/textElement.ts b/packages/ducjs/src/utils/elements/textElement.ts index 9d24c26..573b530 100644 --- a/packages/ducjs/src/utils/elements/textElement.ts +++ b/packages/ducjs/src/utils/elements/textElement.ts @@ -1,15 +1,15 @@ import { TEXT_ALIGN, VERTICAL_ALIGN } from "../../flatbuffers/duc"; +import { SupportedMeasures, getPrecisionValueFromRaw, getScopedBezierPointFromDucPoint } from "../../technical/scopes"; import { DucLocalState, RawValue, Scope, ScopedValue } from "../../types"; import { DucElement, DucElementType, DucPoint, DucTextContainer, DucTextElement, DucTextElementWithContainer, ElementsMap, FontFamilyValues, FontString, NonDeletedDucElement } from "../../types/elements"; import { isArrowElement, isBoundToContainer, isTextElement } from "../../types/elements/typeChecks"; import { GeometricPoint } from "../../types/geometryTypes"; import { ExtractSetType } from "../../types/utility-types"; import { getContainerElement, getElementAbsoluteCoords, getResizedElementAbsoluteCoords } from "../bounds"; -import { ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO, ARROW_LABEL_WIDTH_FRACTION, BOUND_TEXT_PADDING, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, FONT_FAMILY, LEGACY_FONT_ID_TO_NAME, WINDOWS_EMOJI_FALLBACK_FONT } from "../constants"; -import { getBoundTextElementPosition, getPointGlobalCoordinates, getPointsGlobalCoordinates, getSegmentMidPoint } from "./linearElement"; +import { ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO, ARROW_LABEL_WIDTH_FRACTION, BOUND_TEXT_PADDING, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, LEGACY_FONT_ID_TO_NAME, WINDOWS_EMOJI_FALLBACK_FONT } from "../constants"; import { adjustXYWithRotation } from "../math"; import { normalizeText } from "../normalize"; -import { SupportedMeasures, getPrecisionValueFromRaw, getScopedBezierPointFromDucPoint } from "../../technical/scopes"; +import { getBoundTextElementPosition, getPointGlobalCoordinates, getPointsGlobalCoordinates, getSegmentMidPoint } from "./linearElement"; export const computeBoundTextPosition = ( container: DucElement, @@ -73,10 +73,20 @@ export const measureText = ( // lines would be stripped from computation .map((x) => x || " ") .join("\n"); - const fontSize = getPrecisionValueFromRaw(parseFloat(font) as RawValue, currentScope, currentScope); + const parsedFontSize = parseFloat(font); + // Guard: if font string produced an unparseable size (NaN) or zero, + // fall back to DEFAULT_FONT_SIZE so measurements are never degenerate. + const safeFontSize = (Number.isFinite(parsedFontSize) && parsedFontSize > 0) + ? parsedFontSize + : DEFAULT_FONT_SIZE; + const fontSize = getPrecisionValueFromRaw(safeFontSize as RawValue, currentScope, currentScope); const height = getTextHeight(text, fontSize, lineHeight); const width = getTextWidth(text, font); - return { width, height }; + + // Defensive: ensure we never return 0 or NaN dimensions + const safeWidth = (Number.isFinite(width) && width > 0) ? width : 1 as RawValue; + const safeHeight = (Number.isFinite(height) && height > 0) ? height : (safeFontSize * lineHeight) as RawValue; + return { width: safeWidth, height: safeHeight }; }; /** @@ -144,17 +154,17 @@ const getLineWidth = ( // fallback to advance width if the actual width is zero, i.e. on text editing start // or when actual width does not respect whitespace chars, i.e. spaces // otherwise actual width should always be bigger - return Math.max(actualWidth, advanceWidth) as RawValue; + return Math.ceil(Math.max(actualWidth, advanceWidth)) as RawValue; } // since in test env the canvas measureText algo // doesn't measure text and instead just returns number of // characters hence we assume that each letteris 10px if (isTestEnv) { - return advanceWidth * 10 as RawValue; + return Math.ceil(advanceWidth * 10) as RawValue; } - return advanceWidth as RawValue; + return Math.ceil(advanceWidth) as RawValue; }; export const getTextWidth = ( @@ -713,6 +723,20 @@ export const refreshTextDimensions = ( ); } const dimensions = getAdjustedDimensions(textElement, elementsMap, text, currentScope); + + // Defensive minimums — ensure height is always at least one line. + // Use negated >= to also catch NaN (NaN < x is always false). + const rawMinLineHeight = textElement.fontSize.value * textElement.lineHeight; + const minLineHeight = (Number.isFinite(rawMinLineHeight) && rawMinLineHeight > 0) + ? rawMinLineHeight + : DEFAULT_FONT_SIZE * textElement.lineHeight; + if (!(dimensions.height >= minLineHeight)) { + dimensions.height = minLineHeight as RawValue; + } + if (!(dimensions.width > 0)) { + dimensions.width = (textElement.autoResize ? 1 : textElement.width.value) as RawValue; + } + return { text, ...dimensions }; }; From 1bcceb64fa9af9a569ae0cee153eca3436c94989 Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Sat, 21 Feb 2026 10:14:44 +0000 Subject: [PATCH 08/13] refactor: update scopeExponentThreshold default to 3 --- packages/ducjs/src/utils/state/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ducjs/src/utils/state/index.ts b/packages/ducjs/src/utils/state/index.ts index a772b64..92616ac 100644 --- a/packages/ducjs/src/utils/state/index.ts +++ b/packages/ducjs/src/utils/state/index.ts @@ -116,7 +116,7 @@ export const getDefaultGlobalState = (): DucGlobalState => { return { name: null, viewBackgroundColor: typeof window !== "undefined" ? (window.matchMedia("(prefers-color-scheme: dark)").matches ? COLOR_PALETTE.night : COLOR_PALETTE.white) : COLOR_PALETTE.white, - scopeExponentThreshold: 2, + scopeExponentThreshold: 3, mainScope: NEUTRAL_SCOPE, dashSpacingScale: 1 as ScaleFactor, isDashSpacingAffectedByViewportScale: false, From 6d31c27ce1128a45fad9c1c386c2d1439cbf8113 Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Sun, 22 Feb 2026 01:43:53 +0000 Subject: [PATCH 09/13] refactor(restore): handle font family "10" restoration to default font --- packages/ducjs/src/restore/restoreElements.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/ducjs/src/restore/restoreElements.ts b/packages/ducjs/src/restore/restoreElements.ts index d2bd115..6588f1c 100644 --- a/packages/ducjs/src/restore/restoreElements.ts +++ b/packages/ducjs/src/restore/restoreElements.ts @@ -324,6 +324,10 @@ const restoreElement = ( case "text": { let fontSize: PrecisionValue | number = element.fontSize; let fontFamily = element.fontFamily; + // Restore condition: if font family is "10", change to DEFAULT_FONT_FAMILY + if (fontFamily === "10") { + fontFamily = DEFAULT_FONT_FAMILY; + } if ("font" in element) { try { const fontParts = String((element as any).font).split(" "); @@ -2474,10 +2478,15 @@ const restoreTextStyle = ( currentScope: Scope ): DucTextStyle => { const defaultLineHeight = 1.15 as number & { _brand: "unitlessLineHeight" }; + // Restore condition: if font family is "10", change to DEFAULT_FONT_FAMILY + let fontFamily = style?.fontFamily; + if (fontFamily === "10") { + fontFamily = DEFAULT_FONT_FAMILY; + } return { // Text-specific styles isLtr: isValidBoolean(style?.isLtr, true), - fontFamily: getFontFamilyByName(style?.fontFamily as unknown as string), + fontFamily: getFontFamilyByName(fontFamily as unknown as string), bigFontFamily: isValidString(style?.bigFontFamily, "sans-serif"), textAlign: isValidTextAlignValue(style?.textAlign), verticalAlign: isValidVerticalAlignValue(style?.verticalAlign), From a95b86960b8a0a2f1a9e9864c992b34512de9f82 Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Sun, 22 Feb 2026 15:45:49 +0000 Subject: [PATCH 10/13] refactor(freedraw): simplify SVG path generation logic --- .../src/utils/elements/freedrawElement.ts | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/packages/ducjs/src/utils/elements/freedrawElement.ts b/packages/ducjs/src/utils/elements/freedrawElement.ts index 49a21da..bea9e51 100644 --- a/packages/ducjs/src/utils/elements/freedrawElement.ts +++ b/packages/ducjs/src/utils/elements/freedrawElement.ts @@ -32,19 +32,8 @@ function getSvgPathFromStroke(points: number[][]): string { .replace(TO_FIXED_PRECISION, "$1"); } -export function getFreeDrawSvgPath(element: DucFreeDrawElement) { - // If input points are empty (should they ever be?) return a dot - - if(element.points.length === 0) { - return ""; - } - - const inputPoints = element.simulatePressure - ? element.points.map(({x, y}, i) => [x.scoped, y.scoped, element.pressures[i]]) - : element.points.map(({x, y}) => [x.scoped, y.scoped]); - - // Consider changing the options for simulated pressure vs real pressure - const options: StrokeOptions = { +function buildStrokeOptions(element: DucFreeDrawElement): StrokeOptions { + return { size: element.size.scoped, simulatePressure: element.simulatePressure, thinning: element.thinning, @@ -53,7 +42,37 @@ export function getFreeDrawSvgPath(element: DucFreeDrawElement) { easing: element.easing, start: element.start || undefined, end: element.end || undefined, - last: !!element.lastCommittedPoint, // LastCommittedPoint is added on pointerup + last: !!element.lastCommittedPoint, }; +} + +function buildInputPoints(element: DucFreeDrawElement): number[][] { + return element.simulatePressure + ? element.points.map(({x, y}, i) => [x.scoped, y.scoped, element.pressures[i]]) + : element.points.map(({x, y}) => [x.scoped, y.scoped]); +} + +export function getFreeDrawSvgPath(element: DucFreeDrawElement) { + if(element.points.length === 0) { + return ""; + } + + const inputPoints = buildInputPoints(element); + const options = buildStrokeOptions(element); return getSvgPathFromStroke(getStroke(inputPoints, options)); } + +/** + * Returns the raw outline polygon points from perfect-freehand. + * Each point is [x, y]. The result forms a closed polygon that + * represents the visual shape of the freedraw stroke. + */ +export function getFreeDrawStrokePoints(element: DucFreeDrawElement): number[][] { + if (element.points.length === 0) { + return []; + } + + const inputPoints = buildInputPoints(element); + const options = buildStrokeOptions(element); + return getStroke(inputPoints, options); +} From 714fd8dc37c373c551e480bbb5718c0778242119 Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Sun, 22 Feb 2026 15:46:15 +0000 Subject: [PATCH 11/13] fix(ducToSvg): destructure pdfBytes from conversion --- packages/ducpdf/src/duc2pdf/index.ts | 2 +- packages/ducsvg/src/ducToSvg.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ducpdf/src/duc2pdf/index.ts b/packages/ducpdf/src/duc2pdf/index.ts index eba0d0e..1dee3a0 100644 --- a/packages/ducpdf/src/duc2pdf/index.ts +++ b/packages/ducpdf/src/duc2pdf/index.ts @@ -23,7 +23,7 @@ async function initWasm(): Promise { // The wasm-pack generated module exports a default init function // that handles loading the WASM file using import.meta.url - // This is the same pattern used by duc_renderer_bin + // Standard wasm-pack init pattern if (typeof wasmBindings.default === 'function') { // Call the init function - it will automatically fetch the WASM file // using import.meta.url to resolve the path correctly diff --git a/packages/ducsvg/src/ducToSvg.ts b/packages/ducsvg/src/ducToSvg.ts index 03189d5..0f0e35f 100644 --- a/packages/ducsvg/src/ducToSvg.ts +++ b/packages/ducsvg/src/ducToSvg.ts @@ -12,7 +12,7 @@ export const ducToSvg = async ( options?: ConversionOptions, ): Promise => { // Step 1: Convert DUC to PDF using the robust ducpdf converter - const pdfBytes = await convertDucToPdf(ducData, options); + const { data: pdfBytes } = await convertDucToPdf(ducData, options); // Step 2: Convert PDF to SVG pages using hayro-svg const svgDocument = await convertPdfToSvg(pdfBytes); From ea5c9ad34015c49d986849bd98935b7317c19ca8 Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Sun, 22 Feb 2026 18:16:00 +0000 Subject: [PATCH 12/13] feat(pdf_grid): add scale property to DocumentGridConfig --- .../flatbuffers/duc/document-grid-config.ts | 14 +++++++++-- packages/ducjs/src/parse.ts | 3 +++ packages/ducjs/src/restore/restoreElements.ts | 2 ++ packages/ducjs/src/serialize.ts | 1 + packages/ducjs/src/types/elements/index.ts | 1 + .../ducjs/src/utils/elements/newElement.ts | 4 ++-- .../ducpy/src/ducpy/Duc/DocumentGridConfig.py | 15 +++++++++++- .../src/ducpy/builders/element_builders.py | 13 ++++++---- .../ducpy/src/ducpy/classes/ElementsClass.py | 1 + packages/ducpy/src/ducpy/parse.py | 3 +++ packages/ducpy/src/ducpy/serialize.py | 3 ++- .../ducrs/src/flatbuffers/duc_generated.rs | 17 +++++++++++++ packages/ducrs/src/parse.rs | 4 ++++ packages/ducrs/src/serialize.rs | 1 + packages/ducrs/src/types.rs | 1 + schema/duc.fbs | 24 +++++++++++++++++++ 16 files changed, 96 insertions(+), 11 deletions(-) diff --git a/packages/ducjs/src/flatbuffers/duc/document-grid-config.ts b/packages/ducjs/src/flatbuffers/duc/document-grid-config.ts index 819f089..90ee278 100644 --- a/packages/ducjs/src/flatbuffers/duc/document-grid-config.ts +++ b/packages/ducjs/src/flatbuffers/duc/document-grid-config.ts @@ -50,8 +50,13 @@ firstPageAlone():boolean { return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false; } +scale():number { + const offset = this.bb!.__offset(this.bb_pos, 14); + return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0; +} + static startDocumentGridConfig(builder:flatbuffers.Builder) { - builder.startObject(5); + builder.startObject(6); } static addColumns(builder:flatbuffers.Builder, columns:number) { @@ -74,12 +79,16 @@ static addFirstPageAlone(builder:flatbuffers.Builder, firstPageAlone:boolean) { builder.addFieldInt8(4, +firstPageAlone, +false); } +static addScale(builder:flatbuffers.Builder, scale:number) { + builder.addFieldFloat64(5, scale, 0.0); +} + static endDocumentGridConfig(builder:flatbuffers.Builder):flatbuffers.Offset { const offset = builder.endObject(); return offset; } -static createDocumentGridConfig(builder:flatbuffers.Builder, columns:number, gapX:number, gapY:number, alignItems:DOCUMENT_GRID_ALIGN_ITEMS|null, firstPageAlone:boolean):flatbuffers.Offset { +static createDocumentGridConfig(builder:flatbuffers.Builder, columns:number, gapX:number, gapY:number, alignItems:DOCUMENT_GRID_ALIGN_ITEMS|null, firstPageAlone:boolean, scale:number):flatbuffers.Offset { DocumentGridConfig.startDocumentGridConfig(builder); DocumentGridConfig.addColumns(builder, columns); DocumentGridConfig.addGapX(builder, gapX); @@ -87,6 +96,7 @@ static createDocumentGridConfig(builder:flatbuffers.Builder, columns:number, gap if (alignItems !== null) DocumentGridConfig.addAlignItems(builder, alignItems); DocumentGridConfig.addFirstPageAlone(builder, firstPageAlone); + DocumentGridConfig.addScale(builder, scale); return DocumentGridConfig.endDocumentGridConfig(builder); } } diff --git a/packages/ducjs/src/parse.ts b/packages/ducjs/src/parse.ts index 653cd2d..82333a8 100644 --- a/packages/ducjs/src/parse.ts +++ b/packages/ducjs/src/parse.ts @@ -279,6 +279,7 @@ export function parseDocumentGridConfig(gridConfig: DocumentGridConfigFb): Docum return 'start'; })(), firstPageAlone: gridConfig.firstPageAlone(), + scale: gridConfig.scale(), }; } @@ -576,6 +577,7 @@ function parsePdfElement(element: DucPdfElementFb): DucPdfElement { gapY: 0, alignItems: 'start', firstPageAlone: false, + scale: 1, }, }; } @@ -1030,6 +1032,7 @@ function parseDocElement(element: DucDocElementFb): DucDocElement { gapY: 0, alignItems: 'start', firstPageAlone: false, + scale: 1, }, }; } diff --git a/packages/ducjs/src/restore/restoreElements.ts b/packages/ducjs/src/restore/restoreElements.ts index 6588f1c..dcdb923 100644 --- a/packages/ducjs/src/restore/restoreElements.ts +++ b/packages/ducjs/src/restore/restoreElements.ts @@ -1957,6 +1957,7 @@ const restoreDocumentGridConfig = ( gapY: 0, alignItems: "start", firstPageAlone: false, + scale: 1, }; } @@ -1968,6 +1969,7 @@ const restoreDocumentGridConfig = ( ? gridConfig.alignItems : "start", firstPageAlone: typeof gridConfig.firstPageAlone === "boolean" ? gridConfig.firstPageAlone : false, + scale: typeof gridConfig.scale === "number" ? gridConfig.scale : 1, }; }; diff --git a/packages/ducjs/src/serialize.ts b/packages/ducjs/src/serialize.ts index b7251f3..1f70fb9 100644 --- a/packages/ducjs/src/serialize.ts +++ b/packages/ducjs/src/serialize.ts @@ -826,6 +826,7 @@ function writeDocumentGridConfig(b: flatbuffers.Builder, config: DocumentGridCon })(); Duc.DocumentGridConfig.addAlignItems(b, alignItems); Duc.DocumentGridConfig.addFirstPageAlone(b, config.firstPageAlone); + Duc.DocumentGridConfig.addScale(b, config.scale); return Duc.DocumentGridConfig.endDocumentGridConfig(b); } diff --git a/packages/ducjs/src/types/elements/index.ts b/packages/ducjs/src/types/elements/index.ts index 89a8673..167d4fd 100644 --- a/packages/ducjs/src/types/elements/index.ts +++ b/packages/ducjs/src/types/elements/index.ts @@ -531,6 +531,7 @@ export type DocumentGridConfig = { gapY: number; // vertical spacing (px) alignItems: 'start' | 'center' | 'end'; // vertical alignment within row firstPageAlone: boolean; // cover page behavior for 2+ columns + scale: number; // drawing units / real world units } export type DucPdfElement = _DucElementBase & { diff --git a/packages/ducjs/src/utils/elements/newElement.ts b/packages/ducjs/src/utils/elements/newElement.ts index 3b660a2..50e07ed 100644 --- a/packages/ducjs/src/utils/elements/newElement.ts +++ b/packages/ducjs/src/utils/elements/newElement.ts @@ -437,7 +437,7 @@ export const newDocElement = ( columns: opts.columns || { type: COLUMN_TYPE.NO_COLUMNS, definitions: [], autoHeight: true }, autoResize: opts.autoResize ?? true, fileId: null, - gridConfig: { columns: 1, gapX: 0, gapY: 0, alignItems: 'start', firstPageAlone: false }, + gridConfig: { columns: 1, gapX: 0, gapY: 0, alignItems: 'start', firstPageAlone: false, scale: 1 }, // DucDocStyle properties isLtr: opts.isLtr ?? true, fontFamily: opts.fontFamily || DEFAULT_FONT_FAMILY, @@ -458,7 +458,7 @@ export const newDocElement = ( export const newPdfElement = (currentScope: Scope, opts: ElementConstructorOpts): NonDeleted => ({ fileId: null, - gridConfig: { columns: 1, gapX: 0, gapY: 0, alignItems: 'start', firstPageAlone: false }, + gridConfig: { columns: 1, gapX: 0, gapY: 0, alignItems: 'start', firstPageAlone: false, scale: 1 }, ..._newElementBase("pdf", currentScope, opts), type: "pdf", }); diff --git a/packages/ducpy/src/ducpy/Duc/DocumentGridConfig.py b/packages/ducpy/src/ducpy/Duc/DocumentGridConfig.py index b7904a0..522fb33 100644 --- a/packages/ducpy/src/ducpy/Duc/DocumentGridConfig.py +++ b/packages/ducpy/src/ducpy/Duc/DocumentGridConfig.py @@ -63,8 +63,15 @@ def FirstPageAlone(self): return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) return False + # DocumentGridConfig + def Scale(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float64Flags, o + self._tab.Pos) + return 0.0 + def DocumentGridConfigStart(builder): - builder.StartObject(5) + builder.StartObject(6) def Start(builder): DocumentGridConfigStart(builder) @@ -99,6 +106,12 @@ def DocumentGridConfigAddFirstPageAlone(builder, firstPageAlone): def AddFirstPageAlone(builder, firstPageAlone): DocumentGridConfigAddFirstPageAlone(builder, firstPageAlone) +def DocumentGridConfigAddScale(builder, scale): + builder.PrependFloat64Slot(5, scale, 0.0) + +def AddScale(builder, scale): + DocumentGridConfigAddScale(builder, scale) + def DocumentGridConfigEnd(builder): return builder.EndObject() diff --git a/packages/ducpy/src/ducpy/builders/element_builders.py b/packages/ducpy/src/ducpy/builders/element_builders.py index 5798aa7..4a56f84 100644 --- a/packages/ducpy/src/ducpy/builders/element_builders.py +++ b/packages/ducpy/src/ducpy/builders/element_builders.py @@ -322,7 +322,7 @@ def _create_element_wrapper(element_class, base_params, element_params, explicit ) # Create leader style from ducpy.builders.style_builders import (create_simple_styles, - create_text_style) + create_text_style) leader_style = element_params.get('style') if leader_style is None: text_style = create_text_style() @@ -341,12 +341,13 @@ def _create_element_wrapper(element_class, base_params, element_params, explicit content_anchor=content_anchor, content=element_params.get('content') ) + elif element_class == DucDocElement: # Create doc style from ducpy.builders.style_builders import (create_paragraph_formatting, - create_simple_styles, - create_stack_format, - create_text_style) + create_simple_styles, + create_stack_format, + create_text_style) doc_style = element_params.get('style') if doc_style is None: text_style = create_text_style() @@ -361,7 +362,7 @@ def _create_element_wrapper(element_class, base_params, element_params, explicit columns_layout = element_params.get('columns') if columns_layout is None: from ducpy.builders.style_builders import (create_column_layout, - create_text_column) + create_text_column) text_column = create_text_column(width=100.0) columns_layout = create_column_layout(definitions=[text_column]) default_grid_config = DocumentGridConfig( @@ -370,6 +371,7 @@ def _create_element_wrapper(element_class, base_params, element_params, explicit gap_y=0.0, align_items=0, first_page_alone=False, + scale=1.0, ) specific_element = element_class( base=base_element, @@ -1180,6 +1182,7 @@ def build(self) -> ElementWrapper: gap_y=0.0, align_items=0, first_page_alone=False, + scale=1.0, ) element_params = { "file_id": self.extra.get('file_id'), diff --git a/packages/ducpy/src/ducpy/classes/ElementsClass.py b/packages/ducpy/src/ducpy/classes/ElementsClass.py index 8e27350..1683df4 100644 --- a/packages/ducpy/src/ducpy/classes/ElementsClass.py +++ b/packages/ducpy/src/ducpy/classes/ElementsClass.py @@ -446,6 +446,7 @@ class DocumentGridConfig: gap_y: float align_items: DOCUMENT_GRID_ALIGN_ITEMS first_page_alone: bool + scale: float @dataclass class DucPdfElement: diff --git a/packages/ducpy/src/ducpy/parse.py b/packages/ducpy/src/ducpy/parse.py index c3bcf17..09a2595 100644 --- a/packages/ducpy/src/ducpy/parse.py +++ b/packages/ducpy/src/ducpy/parse.py @@ -1236,6 +1236,7 @@ def parse_fbs_document_grid_config(obj: FBSDocumentGridConfig) -> DS_DocumentGri gap_y=obj.GapY(), align_items=obj.AlignItems(), first_page_alone=obj.FirstPageAlone(), + scale=obj.Scale(), ) def parse_fbs_pdf(obj: FBSDucPdfElement) -> DS_DucPdfElement: @@ -1249,6 +1250,7 @@ def parse_fbs_pdf(obj: FBSDucPdfElement) -> DS_DucPdfElement: gap_y=0.0, align_items=0, first_page_alone=False, + scale=1.0, ) return DS_DucPdfElement( base=parse_fbs_duc_element_base(obj.Base()), @@ -1686,6 +1688,7 @@ def parse_fbs_doc(obj: FBSDucDocElement) -> DS_DucDocElement: gap_y=0.0, align_items=0, first_page_alone=False, + scale=1.0, ) return DS_DucDocElement( base=parse_fbs_duc_element_base(obj.Base()), diff --git a/packages/ducpy/src/ducpy/serialize.py b/packages/ducpy/src/ducpy/serialize.py index 280dc56..6bc9d9c 100644 --- a/packages/ducpy/src/ducpy/serialize.py +++ b/packages/ducpy/src/ducpy/serialize.py @@ -577,7 +577,7 @@ ) from ducpy.Duc.DocumentGridConfig import ( DocumentGridConfigStart, DocumentGridConfigAddColumns, DocumentGridConfigAddGapX, DocumentGridConfigAddGapY, - DocumentGridConfigAddAlignItems, DocumentGridConfigAddFirstPageAlone, DocumentGridConfigEnd + DocumentGridConfigAddAlignItems, DocumentGridConfigAddFirstPageAlone, DocumentGridConfigAddScale, DocumentGridConfigEnd ) from ducpy.Duc.DucMermaidElement import ( DucMermaidElementStart, DucMermaidElementAddBase, DucMermaidElementAddSource, DucMermaidElementAddTheme, @@ -1636,6 +1636,7 @@ def serialize_fbs_document_grid_config(builder: flatbuffers.Builder, config: DS_ DocumentGridConfigAddGapY(builder, config.gap_y) DocumentGridConfigAddAlignItems(builder, config.align_items) DocumentGridConfigAddFirstPageAlone(builder, config.first_page_alone) + DocumentGridConfigAddScale(builder, config.scale) return DocumentGridConfigEnd(builder) def serialize_fbs_pdf(builder: flatbuffers.Builder, el: DS_DucPdfElement) -> int: diff --git a/packages/ducrs/src/flatbuffers/duc_generated.rs b/packages/ducrs/src/flatbuffers/duc_generated.rs index 82e4fe7..a148234 100644 --- a/packages/ducrs/src/flatbuffers/duc_generated.rs +++ b/packages/ducrs/src/flatbuffers/duc_generated.rs @@ -14146,6 +14146,7 @@ impl<'a> DocumentGridConfig<'a> { pub const VT_GAP_Y: flatbuffers::VOffsetT = 8; pub const VT_ALIGN_ITEMS: flatbuffers::VOffsetT = 10; pub const VT_FIRST_PAGE_ALONE: flatbuffers::VOffsetT = 12; + pub const VT_SCALE: flatbuffers::VOffsetT = 14; #[inline] pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { @@ -14157,6 +14158,7 @@ impl<'a> DocumentGridConfig<'a> { args: &'args DocumentGridConfigArgs ) -> flatbuffers::WIPOffset> { let mut builder = DocumentGridConfigBuilder::new(_fbb); + builder.add_scale(args.scale); builder.add_gap_y(args.gap_y); builder.add_gap_x(args.gap_x); builder.add_columns(args.columns); @@ -14201,6 +14203,13 @@ impl<'a> DocumentGridConfig<'a> { // which contains a valid value in this slot unsafe { self._tab.get::(DocumentGridConfig::VT_FIRST_PAGE_ALONE, Some(false)).unwrap()} } + #[inline] + pub fn scale(&self) -> f64 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(DocumentGridConfig::VT_SCALE, Some(0.0)).unwrap()} + } } impl flatbuffers::Verifiable for DocumentGridConfig<'_> { @@ -14215,6 +14224,7 @@ impl flatbuffers::Verifiable for DocumentGridConfig<'_> { .visit_field::("gap_y", Self::VT_GAP_Y, false)? .visit_field::("align_items", Self::VT_ALIGN_ITEMS, false)? .visit_field::("first_page_alone", Self::VT_FIRST_PAGE_ALONE, false)? + .visit_field::("scale", Self::VT_SCALE, false)? .finish(); Ok(()) } @@ -14225,6 +14235,7 @@ pub struct DocumentGridConfigArgs { pub gap_y: f64, pub align_items: Option, pub first_page_alone: bool, + pub scale: f64, } impl<'a> Default for DocumentGridConfigArgs { #[inline] @@ -14235,6 +14246,7 @@ impl<'a> Default for DocumentGridConfigArgs { gap_y: 0.0, align_items: None, first_page_alone: false, + scale: 0.0, } } } @@ -14265,6 +14277,10 @@ impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> DocumentGridConfigBuilder<'a, ' self.fbb_.push_slot::(DocumentGridConfig::VT_FIRST_PAGE_ALONE, first_page_alone, false); } #[inline] + pub fn add_scale(&mut self, scale: f64) { + self.fbb_.push_slot::(DocumentGridConfig::VT_SCALE, scale, 0.0); + } + #[inline] pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> DocumentGridConfigBuilder<'a, 'b, A> { let start = _fbb.start_table(); DocumentGridConfigBuilder { @@ -14287,6 +14303,7 @@ impl core::fmt::Debug for DocumentGridConfig<'_> { ds.field("gap_y", &self.gap_y()); ds.field("align_items", &self.align_items()); ds.field("first_page_alone", &self.first_page_alone()); + ds.field("scale", &self.scale()); ds.finish() } } diff --git a/packages/ducrs/src/parse.rs b/packages/ducrs/src/parse.rs index c1b1cfd..d66d00a 100644 --- a/packages/ducrs/src/parse.rs +++ b/packages/ducrs/src/parse.rs @@ -692,6 +692,7 @@ fn parse_duc_pdf_element(el: fb::DucPdfElement) -> ParseResult types::DocumentGridConfig { columns: 1, @@ -699,6 +700,7 @@ fn parse_duc_pdf_element(el: fb::DucPdfElement) -> ParseResult ParseResult types::DocumentGridConfig { columns: 1, @@ -1204,6 +1207,7 @@ fn parse_duc_doc_element(el: fb::DucDocElement) -> ParseResult( gap_y: config.gap_y, align_items: Some(config.align_items.into()), first_page_alone: config.first_page_alone, + scale: config.scale, }, ) } diff --git a/packages/ducrs/src/types.rs b/packages/ducrs/src/types.rs index 2ca6add..8245d10 100644 --- a/packages/ducrs/src/types.rs +++ b/packages/ducrs/src/types.rs @@ -600,6 +600,7 @@ pub struct DocumentGridConfig { pub gap_y: f64, pub align_items: DocumentGridAlignItems, pub first_page_alone: bool, + pub scale: f64, } /// Vertical alignment for document grid layout diff --git a/schema/duc.fbs b/schema/duc.fbs index a8c562c..8c14a77 100644 --- a/schema/duc.fbs +++ b/schema/duc.fbs @@ -1453,6 +1453,30 @@ table DocumentGridConfig { align_items: DOCUMENT_GRID_ALIGN_ITEMS = null; /** cover page behavior for 2+ columns */ first_page_alone: bool; + /** + * The scale factor of the element, represented as a single floating-point number. + * This is calculated as: (Drawing Units ÷ Real World Units). + * + * The scale factor is strictly a ratio and is unitless. + * + * @example + * // Reduction (Architecture / Maps) - Drawing is smaller than reality + * // 1:300 scale -> 1 / 300 = 0.00333333 + * // 1/4" = 1'-0" -> 1 / 48 = 0.02083333 (Imperial 1:48) + * + * @example + * // Neutral - Drawing is exact real-world size + * // 1:1 scale -> 1 / 1 = 1.0 + * + * @example + * // Enlargement (Machining / Details) - Drawing is bigger than reality + * // 5:1 scale -> 5 / 1 = 5.0 + * + * @remarks + * To calculate the real-world size from a measurement taken on the PDF: + * `realWorldSize = pdfMeasurement / scale` + */ + scale: double; } table DucPdfElement { From ecdafd639db190b2388777e5ee31b65709591919 Mon Sep 17 00:00:00 2001 From: Jorge Soares Date: Mon, 23 Feb 2026 12:12:43 +0000 Subject: [PATCH 13/13] refactor: add type checks for PDF elements --- packages/ducjs/src/types/elements/typeChecks.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/ducjs/src/types/elements/typeChecks.ts b/packages/ducjs/src/types/elements/typeChecks.ts index 69a3ad5..83fbc72 100644 --- a/packages/ducjs/src/types/elements/typeChecks.ts +++ b/packages/ducjs/src/types/elements/typeChecks.ts @@ -5,6 +5,7 @@ import { Bounds, LineSegment, TuplePoint } from "../geometryTypes"; import type { DucArrowElement, DucBindableElement, + DucDocElement, DucElbowArrowElement, DucElement, DucElementType, @@ -15,6 +16,7 @@ import type { DucFreeDrawElement, DucImageElement, DucLinearElement, + DucPdfElement, DucPlotElement, DucTableElement, DucPointBinding, @@ -42,6 +44,20 @@ export const isImageElement = ( return !!element && element.type === "image"; }; +export const isPdfElement = ( + element: DucElement | null, +): element is DucPdfElement => { + return !!element && element.type === "pdf"; +}; + +export type DucPdfLikeElement = DucPdfElement | DucDocElement; + +export const isPdfLikeElement = ( + element: DucElement | null, +): element is DucPdfLikeElement => { + return !!element && (element.type === "pdf" || element.type === "doc"); +}; + export const isEmbeddableElement = ( element: DucElement | null | undefined, ): element is DucEmbeddableElement => {