Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/serialization/serialize-altium-binary-documents.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export { serializeAltiumPcbDocToBinary } from "./serialize-altium-pcb-doc-to-binary"
export { serializeAltiumSchDocToBinary } from "./serialize-altium-sch-doc-to-binary"
export {
type AltiumSchematicEmbeddedImageInput,
type SerializeAltiumSchDocToBinaryOptions,
serializeAltiumSchDocToBinary,
} from "./serialize-altium-sch-doc-to-binary"
49 changes: 46 additions & 3 deletions lib/serialization/serialize-altium-sch-doc-to-binary.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { AltiumSchDoc } from "../altium-sch-doc"
import { encodeAltiumText } from "../parser/decode-altium-text"
import {
addAltiumCompoundStream,
concatAltiumBinaryBytes,
Expand All @@ -11,9 +12,19 @@ import {
toAltiumBinaryRecordBytes,
} from "./altium-binary-record-encoding"

export type AltiumSchematicEmbeddedImageInput = {
compressedBytes: Uint8Array
name: string
}

export type SerializeAltiumSchDocToBinaryOptions = {
embeddedImages?: readonly AltiumSchematicEmbeddedImageInput[]
}

/** Encodes an ASCII schematic into Altium's native OLE/CFB SchDoc container. */
export function serializeAltiumSchDocToBinary(
document: string | AltiumSchDoc,
options: SerializeAltiumSchDocToBinaryOptions = {},
): Uint8Array {
const asciiDocument = getAsciiAltiumSource(document)
const recordSources = asciiDocument
Expand Down Expand Up @@ -41,9 +52,7 @@ export function serializeAltiumSchDocToBinary(
})
addAltiumCompoundStream({
compoundFile,
content: toLengthPrefixedTextBlock(
toAltiumBinaryRecordBytes("|HEADER=Icon storage"),
),
content: serializeSchematicImageStorage(options.embeddedImages ?? []),
path: "/Storage",
})
addAltiumCompoundStream({
Expand All @@ -56,6 +65,40 @@ export function serializeAltiumSchDocToBinary(
return writeAltiumCompoundFile(compoundFile)
}

function serializeSchematicImageStorage(
embeddedImages: readonly AltiumSchematicEmbeddedImageInput[],
): Uint8Array {
const header = toLengthPrefixedTextBlock(
toAltiumBinaryRecordBytes("|HEADER=Icon storage"),
)
const entries = embeddedImages.map(({ compressedBytes, name }) => {
const nameBytes = encodeAltiumText(name, "windows-1252")
if (nameBytes.byteLength === 0 || nameBytes.byteLength > 255) {
throw new RangeError(
"Embedded schematic image names must be 1 to 255 Windows-1252 bytes",
)
}
if (compressedBytes.byteLength === 0) {
throw new RangeError(
"Embedded schematic image compressed bytes must not be empty",
)
}
const entry = concatAltiumBinaryBytes([
Uint8Array.of(0xd0, nameBytes.byteLength),
nameBytes,
uint32AltiumBytes(compressedBytes.byteLength),
compressedBytes,
])
if (entry.byteLength > 0x00ff_ffff) {
throw new RangeError(
"Embedded schematic image storage entries must fit in 24 bits",
)
}
return concatAltiumBinaryBytes([uint32AltiumBytes(entry.byteLength), entry])
})
return concatAltiumBinaryBytes([header, ...entries])
}

function toLengthPrefixedTextBlock(recordBytes: Uint8Array): Uint8Array {
return concatAltiumBinaryBytes([
uint32AltiumBytes(recordBytes.byteLength),
Expand Down
21 changes: 21 additions & 0 deletions tests/binary-schematic-document-creation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,24 @@ test("creates native binary schematic documents", () => {
expect(document.components).toHaveLength(1)
expect(document.components[0]?.getDecoded("COMMENT")).toBe("10k Ω")
})

test("creates native schematic image storage", () => {
const compressedBytes = Uint8Array.of(0x78, 0x9c, 0x01, 0x02, 0x03)
const bytes = serializeAltiumSchDocToBinary(
[
"|HEADER=Protel for Windows - Schematic Capture Ascii File Version 5.0|WEIGHT=2",
"|RECORD=31|CUSTOMX=1000|CUSTOMY=800|USECUSTOMSHEET=T",
"|RECORD=30|OWNERINDEX=-1|FILENAME=logo.png|LOCATION.X=10|LOCATION.Y=10|CORNER.X=20|CORNER.Y=20",
].join("\r\n"),
{
embeddedImages: [{ compressedBytes, name: "logo.png" }],
},
)
const document = parseAltiumSchDoc(bytes)

expect(document.embeddedImages).toHaveLength(1)
expect(document.embeddedImages[0]?.name).toBe("logo.png")
expect(document.embeddedImages[0]?.getCompressedBytes()).toEqual(
compressedBytes,
)
})
Loading