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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ On desktop, **Connect data** also reads an Excel worksheet. **File → Settings

- The **Objects** group selects the fonts and graphics the **Setup Script** uploads.
- It lists cached images and deletes the unused ones.
- On desktop it also reads what the printer holds. You can show a stored graphic, let the printer draw a font sample and delete objects on the printer.
- On desktop it also reads what the printer holds. Show a stored graphic or let the printer draw a font sample. Delete objects on the printer.
- Saved designs and label exports exclude **Setup Script** values such as printer name and locale.
- **Clear** resets the **Setup Script** values and keeps the uploads.

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/lib/fontSample.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FONT_SAMPLE_TEXT, fontSampleZpl } from "./fontSample";
const media = { widthMm: 100, heightMm: 150, dpmm: 8 };

describe("fontSampleZpl", () => {
it("sets the font from the printer on a label of the media size, with a line budget the text cannot exceed", () => {
it("sets the printer font on a label of the media size, with a line budget the text cannot exceed", () => {
expect(fontSampleZpl("E:CG_TIMES.TTF", media)).toBe(`^XA^PW800^LL1200^FO40,40^A@N,60,,E:CG_TIMES.TTF^FB720,${FONT_SAMPLE_TEXT.length},0,L^FD${FONT_SAMPLE_TEXT}^FS^XZ`);
});

Expand All @@ -17,7 +17,7 @@ describe("fontSampleZpl", () => {
expect(fontSampleZpl("E:A.FNT", { widthMm: 1, heightMm: 1, dpmm: 6, jmDensity: "B" })).toContain("^FO0,0^A@N,1,,E:A.FNT^FB3,");
});

it("declares half density and sizes the field in that space while the media keeps head dots", () => {
it("declares half density and sizes the field in that space while ^PW and ^LL keep head dots", () => {
expect(fontSampleZpl("E:A.FNT", { ...media, jmDensity: "B" })).toContain("^XA^JMB^PW800^LL1200^FO20,20^A@N,30,,E:A.FNT^FB360,");
});

Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/lib/fontSample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@ export interface SampleMedia {

/** A label that sets one printer font, sized to the media so the firmware renders it like a job. */
export function fontSampleZpl(fontPath: string, media: SampleMedia): string {
// ^PW and ^LL stay in head dots, the field lives in the ^JM-scaled space like every generated object.
const headWidth = mmToDots(media.widthMm, media.dpmm);
const headHeight = mmToDots(media.heightMm, media.dpmm);
const dpmm = effectiveDpmm(media);
const width = mmToDots(media.widthMm, dpmm);
// The shorter edge bounds margin and size, so a short or narrow label still holds the field.
// The shorter edge bounds size and margin, so a short label still holds the field.
const edge = Math.min(width, mmToDots(media.heightMm, dpmm));
const size = Math.max(1, Math.min(mmToDots(7.5, dpmm), Math.floor(edge / 4)));
const margin = Math.min(mmToDots(5, dpmm), Math.floor(edge / 10));
// Every line past the ^FB budget overprints the last one, so the budget is the text length.
// A block under the font width prints nothing and lines past the budget overprint the last (spec p.186).
const block = `^FB${Math.max(width - 2 * margin, size)},${FONT_SAMPLE_TEXT.length},0,L`;
const density = media.jmDensity === "B" ? "^JMB" : "";
return `^XA${density}^PW${headWidth}^LL${headHeight}^FO${margin},${margin}^A@N,${size},,${stripZplParamChars(fontPath)}${block}^FD${FONT_SAMPLE_TEXT}^FS^XZ`;
Expand Down
3 changes: 1 addition & 2 deletions src/components/PrinterSettings/PrinterImageDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import { PrinterReplyDialog } from "../Output/PrinterReplyDialog";
interface Props {
path: string;
read: PrinterOutcome<string | null> | "reading";
/** What to say for null, since a blank sample and an unreadable graphic differ. */
/** Null is a blank sample for a font and an unreadable raster for a graphic, so the caller words it. */
blankText: string;
onClose: () => void;
}

/** A stored graphic or a font sample as the printer draws it. */
export function PrinterImageDialog({ path, read, blankText, onClose }: Props) {
return (
<PrinterReplyDialog title={path} titleClassName="font-mono" portal read={read} onClose={onClose}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe("PrinterObjectsSection", () => {
expect(fonts.queryByText("R:PRE.GRF")).toBeNull();
});

it("shows a stored graphic in a dialog and names a raster it cannot draw", async () => {
it("shows a stored graphic in a dialog and names one it cannot draw", async () => {
listed();
readPrinterObjectImage.mockResolvedValueOnce({ kind: "ok", value: "data:image/png;base64,x" });
const r = render(<PrinterObjectsSection kind="graphic" />);
Expand Down
4 changes: 2 additions & 2 deletions src/lib/printerObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export async function readPrinterGraphic(target: QueryTarget, object: HostObject
return graphic ? { kind: "ok", value: graphic } : { kind: "unparsed" };
}

/** The printer renders the sample itself, so the font shows as the firmware draws it. Null when it drew nothing. */
/** The sample the printer draws for one font, cropped to the ink. Null when it drew nothing. */
export async function readPrinterFontSample(target: QueryTarget, object: HostObject, media: SampleMedia): Promise<PrinterOutcome<PrinterBitmap | null>> {
const res = await fetchPrinterPreview(target, fontSampleZpl(hostObjectPath(object), media));
if (res.kind !== "ok") return res;
Expand All @@ -42,7 +42,7 @@ function graphicDataUrl(graphic: HostGraphic): string | null {
return raster ? bitmapToDataUrl({ width: raster.paddedWidth, height: raster.heightDots, mono: raster.bytes }) : null;
}

/** The picture of an object by its kind, as a data URL. Null when the printer gave nothing drawable. */
/** A data URL of the object, a stored graphic or a drawn font sample. Null when nothing is drawable. */
export async function readPrinterObjectImage(target: QueryTarget, object: HostObject, media: SampleMedia): Promise<PrinterOutcome<string | null>> {
if (hostObjectKind(object.ext) === "font") {
const sample = await readPrinterFontSample(target, object, media);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/zebraGraphic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const dyResponse = (mono: Uint8Array, kind: "Z" | "B", wrap = false): string =>
};

describe("buildPrinterPreviewZpl", () => {
it("injects ^IS before the final ^XZ and appends the ^HY upload", () => {
it("stores under a fresh name per render, uploads it with ^HY and deletes it with ^ID", () => {
const random = vi.spyOn(Math, "random").mockReturnValueOnce(0.5).mockReturnValueOnce(0.25);
const out = buildPrinterPreviewZpl("^XA^FO10,10^A0N,30^FDHi^FS^XZ");
expect(out).toContain("^ISR:ZL800000.GRF,N^XZ");
Expand Down
13 changes: 6 additions & 7 deletions src/lib/zebraGraphic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@ export interface PrinterBitmap {
mono: Uint8Array;
}

/** Wrap a design's ZPL so the printer renders it to a stored graphic (^IS, with
* print suppressed) and then uploads that graphic to the host (^HY). The reply
* is a ~DY the caller decodes with {@link decodeDyGraphic}. `^IS` is injected
* before the label's final `^XZ` so it captures the rendered format. */
/** Wrap a design so the printer renders it into a scratch graphic (^IS with print suppressed),
* uploads it (^HY) and drops it (^ID). The `^IS` goes before the label's final `^XZ` so it
* captures the rendered format. The reply is a ~DY for {@link decodeDyGraphic}. */
export function buildPrinterPreviewZpl(designZpl: string): string {
// `^LL` applies to continuous media only; `,Y` forces the design length onto
// gap/notch media too. Preview-only; the print path respects real media.
const sized = designZpl.replace(/(\^LL\d+)(?![\d,])/g, "$1,Y");
// A name of its own per render, so the scratch never overwrites an object the user keeps on R:.
// A fixed name would overwrite a graphic the user keeps on R:.
const scratch = `R:ZL${Math.floor(Math.random() * 0x1000000).toString(16).toUpperCase().padStart(6, "0")}.GRF`;
const store = /\^XZ\s*$/.test(sized) ? sized.replace(/\^XZ\s*$/, `^IS${scratch},N^XZ`) : `${sized}^IS${scratch},N^XZ`;
// The ^ID rides in the same transmission, so the scratch never stays on the printer.
// The ^ID rides in the same transmission, so the scratch goes without a second query.
return `${store}\n^XA^HY${scratch}^XZ^XA^ID${scratch}^FS^XZ`;
}

Expand Down Expand Up @@ -78,7 +77,7 @@ export interface ContentBounds {
bottom: number;
}

/** The bounds widened to whole bytes, so the rows copy without re-packing bits. */
/** Crop to the bounds, widened to whole bytes so the rows copy without re-packing bits. */
export function cropBitmap(bmp: PrinterBitmap, bounds: ContentBounds): PrinterBitmap {
const firstByte = Math.floor(bounds.left / 8);
const lastByte = Math.ceil(bounds.right / 8);
Expand Down
2 changes: 1 addition & 1 deletion src/store/slices/printerObjectsSlice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe("readPrinterObjects", () => {
});

describe("readPrinterObjectImage", () => {
it("hands the image back on the page's media without touching the listing", async () => {
it("hands the image back on the page's media, names the step per kind and leaves the listing", async () => {
let step: string | undefined;
readPrinterObjectImage.mockImplementation(() => {
step = useLabelStore.getState().printerReading;
Expand Down
2 changes: 1 addition & 1 deletion src/store/slices/printerObjectsSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type PrinterObjectsState =
export interface PrinterObjectsSlice {
printerObjects: PrinterObjectsState;
readPrinterObjects: () => Promise<void>;
/** A graphic as stored, or a font drawn by the printer on the page's media. */
/** Fonts are sampled on the current page's media. */
readPrinterObjectImage: (object: HostObject) => Promise<PrinterOutcome<string | null>>;
/** The fresh listing tells whether the printer took the delete (spec p.246 ignores unknown names). */
deletePrinterObject: (object: HostObject) => Promise<PrinterQueryFailure | undefined>;
Expand Down
Loading