Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
245b045
Make barcode sizing understandable and printer-aware
glenntosco Oct 12, 2026
c369350
fix: a data load stays busy until its records are in place
glenntosco Oct 12, 2026
43ca05c
Merge branch 'feature/barcode-size'
glenntosco Oct 12, 2026
aaa31f3
feat: relative position with a nine-point anchor
glenntosco Oct 12, 2026
f352c77
Merge branch 'feature/relative-position'
glenntosco Oct 12, 2026
6936826
feat: editable ZPL source panel and ^FN field round trip
glenntosco Oct 12, 2026
23b58a0
Merge branch 'feature/zpl-source-panel'
glenntosco Oct 12, 2026
40cdd30
fix: separate the ZPL panel's hint line from the code
glenntosco Oct 12, 2026
d45ead1
Merge the designer work of 2026-10-12: barcode size, relative positio…
glenntosco Oct 27, 2026
bbc3708
Anchor a barcode by its measured width
glenntosco Oct 27, 2026
45bff43
Printing: abort unfinished spooler jobs, count in-flight TCP bytes, r…
glenntosco Nov 9, 2026
9d1a641
Rendering: refuse oversize images with a placeholder and cache dither…
glenntosco Nov 9, 2026
0e0e48b
Harden the SQL read-only guard against lexer-differential bypasses
glenntosco Nov 9, 2026
23794d0
MCP print gate: scan all ZPL for printer-changing commands, require a…
glenntosco Nov 9, 2026
195b839
Guard PostgreSQL text-running and file functions on ODBC connections too
glenntosco Nov 9, 2026
1364fc8
Review follow-up: placeholder issue in exports, more printer commands…
glenntosco Nov 9, 2026
1d5c3b1
Bound memory use on untrusted input and never reissue serial numbers
glenntosco Nov 9, 2026
118e846
Merge branch 'fix/review-data' into fix/review-hardening
glenntosco Nov 9, 2026
1622c56
Merge branch 'fix/review-core' into fix/review-hardening
glenntosco Nov 9, 2026
8a7252f
Merge branch 'fix/review-print' into fix/review-hardening
glenntosco Nov 9, 2026
3e38a20
Rendering: use the same 40 million pixel image limit as Core
glenntosco Nov 9, 2026
a16c9e2
Merge branch 'fix/review-print' into fix/review-hardening
glenntosco Nov 9, 2026
8fd4b1b
Write settings.json through a temporary file so a crash cannot trunca…
glenntosco Nov 9, 2026
1d69b7c
Give back serial numbers when a counter run cannot start, and report …
glenntosco Nov 10, 2026
10ae4b6
Move the printer command scanner to Rotula.Printing and tag each comm…
glenntosco Nov 10, 2026
b9ccf1e
test: report a check that could not run as skipped, not passed
glenntosco Nov 10, 2026
b4a771b
test: give the LocalDB listing tests an instance of their own per run
glenntosco Nov 10, 2026
3f16025
test: check that every element property survives clone, save, paste a…
glenntosco Nov 10, 2026
9422626
ci: add weekly CodeQL analysis and Dependabot updates
glenntosco Nov 10, 2026
4fe80a5
Merge branch 'fix/wave2-core2' into fix/review-hardening
glenntosco Nov 10, 2026
b7dda9d
Merge branch 'fix/wave2-app' into fix/review-hardening
glenntosco Nov 10, 2026
b913a83
Merge branch 'fix/wave2-ci' into fix/review-hardening
glenntosco Nov 10, 2026
364bf3c
Print through any Windows printer, not only ZPL ones
glenntosco Nov 10, 2026
524a603
Merge origin/main into release/0.4.0
glenntosco Nov 10, 2026
65aceaf
Prepare 0.4.0: version, changelog and compare links
glenntosco Nov 10, 2026
31904e1
Fix the anchored golden label: Code 128 SKU-0042 is 224 dots wide, no…
glenntosco Nov 10, 2026
c51e3b4
Remove the CodeQL workflow: the repository already runs CodeQL throug…
glenntosco Nov 10, 2026
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
22 changes: 22 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Keeps third-party dependencies current. Dependabot only opens pull requests; nothing here builds, publishes or signs anything.
version: 2
updates:
# NuGet: one grouped pull request per week for minor and patch updates, so the review stays small.
# Major versions are left as separate pull requests on purpose: they can change behaviour and deserve their own look
# (and, for a dependency that ships inside the app, a licence check against THIRD-PARTY-NOTICES.md).
- package-ecosystem: "nuget"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
groups:
nuget-minor-and-patch:
update-types:
- "minor"
- "patch"

# GitHub Actions: the workflows pin actions by version tag, so a monthly check is enough to pick up new releases.
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
27 changes: 26 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

## [0.4.0] - 2026-11-10

### Added
- **Print to any Windows printer.** A DYMO, Brother, TSC, SATO, Epson or office printer that does not understand ZPL can now print your labels through its Windows driver. Each printer has a **print mode**: *Printer language (ZPL)*, as before, or *Windows driver (picture)*, which draws the label at the printer's real resolution and the label's real size. The mode is remembered per printer. The first time you print to a printer with no saved mode, the Print dialog asks once "How does this printer understand labels?" and suggests an answer from the printer's name and driver (ZPL for Zebra-style names and "Generic / Text Only" queues); network printers added in Rotula by address (TCP/IP, port 9100) always get ZPL. Nothing is switched for you: the printer saved in your settings stays on ZPL without a question, and other printers you used before this version are asked about once, with ZPL already selected for Zebra-style names. Before a picture print, Rotula lists what a driver cannot print the same way (raw ZPL kept from an imported file, barcodes only a ZPL printer can draw, recalls of graphics stored on the printer, images too large to draw, UL and CSA marks, a barcode whose bars are narrower than their safe size, a label larger than the area the printer can print on, whose outer edge may be cut off) and asks for one tick for the whole job; a label larger than the paper the driver uses is refused; a field the printer would number, date or fill in itself (`^SN`/`^SF`, `^FC`, `^FE`) cannot be printed this way and is refused. It works for a single label, for a whole data-merge batch (checked once, not per record) and for the print station. Because every copy is a page of its own in the Windows print queue, one job may print at most 100,000 labels (all the ticked records times their copies); more is refused with a plain message that says to print in smaller batches, and no tick lets it through. A label that does not fit the driver's paper but fits it turned a quarter turn (sideways stock) is turned clockwise for you, pixel for pixel so barcodes stay exact, and Rotula says so before printing (one tick); when neither way fits you get the usual paper error. The preview says it shows the printed picture. Printer-side counters, cutter, tear-off and status checks stay ZPL-only; the MCP server and the command line print ZPL only, and ZPL output does not change. No label printer to hand? Choose "Microsoft Print to PDF" and the Windows driver mode to try it. See [docs/printing.md](docs/printing.md).
- **Set up print station** (File > Set up print station...): checks every label of a print station with the printer it uses and, for a printer in Windows driver mode, shows the points a driver cannot print the same way. When the label is fine as it is, you tick one box and save, and operators can print it. The acceptance is kept in `station.acknowledged.json` next to `station.json` (which is never changed). If the points found for a label change after you accepted them, operators are told to ask you to check it again. See [docs/print-station.md](docs/print-station.md).
- **Print engine for scripts and warehouse systems** (`rotula.exe`): print `.rotula` labels with no window. `rotula print` takes a label, field values (`--set`) or records from a CSV, Excel or JSON file, and a Windows printer or network address, and prints through the designer's own print path: resolution conversion, darkness and speed, variables, questions (`--answer`, otherwise the designer default; never a sample value), copies, and serial numbers used once. Before every job it checks the printer status (`~HS`) and holds the job back on a fault; after the last job it waits until the printer reports it finished. `rotula status` and `rotula printers` report printers, `--json` gives a machine-readable result, and exit codes tell a script what happened. `rotula serve` offers the same as a REST API (`/api/v1/jobs`, printer status and lists, OpenAPI at `/openapi/v1.json`) that can run as a Windows service: off until started, on `127.0.0.1` by default, an API key on every call (only its hash is stored), and only the label folders and printers its settings file lists. One job at a time per printer. Messages in English, Spanish, Portuguese (Brazil) and French (Canada). A new `PrintEngine` policy lets an administrator switch it off. The setup program, the MSI and the portable zip include `rotula.exe` beside the app. See [docs/print-engine.md](docs/print-engine.md).
- **Label sheets for office printers** (File > Export > Label sheets (PDF), or Label sheets in the Print dialog): put the label on sheets for a laser or inkjet printer and save them as a PDF at the sheet's real size, each label at its own physical size. Pick a generic template (Letter 30-up, 20-up, 14-up, 10-up, 6-up, 80-up and half sheets; A4 3 x 8, 21-up, 65-up, 16-up, 14-up, 8-up and 2-up) or type your own layout, start at any position of a partly used sheet, and add crop marks or label outlines. With data loaded the ticked records go on the sheets with their copies, and serial numbers are used once, exactly as when printing. The layout is saved with the label; older versions of Rotula open such a file and ignore it. Sending the sheets straight to a Windows printer is not offered: print the PDF at actual size.
- **Machine-wide MSI for IT departments**: every release now also has `Rotula-X.Y.Z-win-x64.msi`, signed like the setup program. It installs to Program Files for every user with a Start menu entry and the `.rotula` association, upgrades older versions in place, and is made for silent deployment with Intune, Group Policy or Configuration Manager. The setup program can now also install for all users silently with `/ALLUSERS`. Silent install and uninstall commands, exit codes, Intune detection rules and Group Policy steps are in the new [docs/deployment.md](docs/deployment.md).
Expand All @@ -19,10 +23,22 @@ and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- **Print an object only when you want it**: in the new Printing section of the properties, mark an object "Do not print this object" (a note or guide that never reaches the printer or an export), set "Print only when" a field equals, does not equal, contains, is empty or is not empty a value (so a warning can appear on some labels only), and "Lock position" so nothing moves it by accident. The Objects list and the canvas show small No print, Hidden and Locked badges.
- **The Preview window shows a label the way it prints when it is set to print upside down (`^POI`) or mirrored (`^PMY`).** It opens on "As printed", says in one sentence why the picture is turned, and has an "As designed" switch to see it upright. The canvas stays upright for editing and the status bar says "Prints upside down" (or "Prints mirrored"). Imported ZPL with `^POI` / `^PON` and `^PMY` sets the label's setting, so the preview follows it. PNG, JPEG and PDF exports stay as designed.
- **Portuguese (Brazil) and French (Canada)**: the whole interface is now also available in Brazilian Portuguese and Canadian French (View > Language, where each language is listed in its own name), and the installer offers both. On first run Rotula follows Windows: a Portuguese desktop gets Portuguese (Brazil), a French one French (Canada), Spanish stays Spanish and anything else English. Numbers and dates follow the chosen language. A language you picked before stays as it was. The "Language of Rotula" policy (Group Policy / Intune) also accepts `pt-BR` and `fr-CA`.
- **The ZPL panel is editable.** Type or paste ZPL under the canvas and, after a short pause, the label is rebuilt from it through the same importer as File > Import ZPL; every edit is one undo step (Ctrl+Z works from inside the panel). A badge says whether the text is *Generated* from the design or *Edited by hand - re-imported*; "Revert to design" regenerates it. Commands the designer does not model are never dropped: the status line says how many were kept as raw ZPL and names the first ones (hover for every notice). Element names, data bindings, print conditions, locks, text fit and "do not print" objects survive an edit when the element is still there. A hint line explains the command under the caret: its name, its parameters and what each one means, for every command Rotula supports.
- **Numbered fields (`^FN`) are data fields.** Importing ZPL turns `^FN3"Customer name"` into the placeholder `{{Customer_name}}` (or `{{Field3}}` without a prompt) with the field's data as the sample value, so the field can be merged from a file or database; the number and prompt are written back unchanged. A label can be written as a **stored format** (`ZplGenerator.GenerateStoredFormat`: `^DF` plus one `^FN` per placeholder) and its **recall** (`GenerateFormatRecall`: `^XF` plus the values), with the same numbering both ways.
- **Barcode size you can understand, and checked against the printer.** Under the module width of a barcode (and the module size of QR Code, Data Matrix and Aztec) the Properties panel now says what the setting means on paper: "Module: 9.9 mil (0.25 mm) - symbol 17 mm wide", plus the quiet zone the symbol needs (for example 10X on each side for Code 128, 11X before and 7X after for EAN-13, 4X for QR Code), and a small - / + pair changes the module one dot at a time with the readout following live. While one barcode is selected the design canvas shows its quiet zone as a dotted rectangle. The Problems list now warns, in plain words, when the module is a single dot at 300 dpi or more, when a linear code has bars thinner than 2 dots at 203 dpi, and when the symbol plus its quiet zones does not fit the label width. Nothing about the file format changed.
- **Relative position with an anchor point** (Properties panel, Position): pick one of nine anchor points (corners, edge middles, centre) and what the object is relative to, the label or another object. X and Y then mean the distance between the two anchor points, so an object anchored bottom-right stays in the bottom-right corner when the label is resized, and an object relative to another one follows it when it moves or grows (a longer text on one record moves its right-anchored neighbour on that record only). Choosing an anchor never moves the object: only the numbers change. The canvas marks the anchor point of the selected object, deleting a reference keeps its followers where they are, and a reference that would make two objects relative to each other is refused with an explanation. The printer always receives absolute `^FO`/`^FT` positions. Objects without an anchor behave exactly as before and such labels are saved exactly as before; a label that uses anchors is saved as format version 11, which older versions of Rotula refuse to open. Also new: **Align to label** (Object menu) lines several selected objects up with the label's edges or centre instead of with each other.

### Fixed
- **Windows fonts that are named after a style now draw in that style.** "Arial Black", "Segoe UI Semibold", "Segoe UI Black", "Bahnschrift Condensed" and similar names used to be replaced by the default font in the preview, PNG/PDF export and print picture; they now resolve to their parent family at the weight and width the name says.
- **Arial Narrow, Franklin Gothic Medium and other families Windows lists only under their own name now draw in that font** instead of the default one: when the font manager does not know the family, Rotula loads the installed font file.
- **Serial numbers are never reissued, and are given back when a job cannot start.** A number that may have reached the printer is never used again. If the counter store cannot be read or saved (full disk, locked file, no permission), the run stops before anything prints, the numbers already taken are given back, and the message says what happened and what to check, instead of an error window.
- **Printing is more careful when something goes wrong.** An unfinished job is cancelled in the Windows print queue instead of being left behind, bytes still on their way to a network printer are counted, and a printer that cannot start the job is reported as a result with a reason.
- **Settings are saved safely.** `settings.json` is written to a temporary file first and then swapped in, so a crash or power cut during the save cannot leave it half written.
- **Very large or hostile input no longer exhausts memory.** Images above 40 million pixels are refused with a placeholder (the same limit in the preview, exports and printing), and imports and data loads are bounded.

### Security
- **The read-only guard for database sources is stricter.** It now refuses spellings of a query that different SQL readers could understand differently, and refuses PostgreSQL functions that run text or read files on ODBC connections too. A few unusual but valid queries may be refused: for example `SELECT 1AS x` (no space) must be written `SELECT 1 AS x`.
- **The MCP server checks every label it is asked to print.** All the ZPL is scanned for commands that change the printer itself (memory, network, stored settings); such a job needs an explicit acknowledgement, and raw ZPL blocks are shown in full before printing.

## [0.3.5] - 2026-10-12

Expand Down Expand Up @@ -143,6 +159,15 @@ First public version.
- Releases are signed with Azure Artifact Signing, but Windows SmartScreen can still show a reputation warning for a new,
little-downloaded file until downloads build trust (More info, then Run anyway).

[Unreleased]: https://github.com/P4Software/Rotula/compare/v0.1.1...HEAD
[Unreleased]: https://github.com/P4Software/Rotula/compare/v0.4.0...HEAD
[0.4.0]: https://github.com/P4Software/Rotula/compare/v0.3.5...v0.4.0
[0.3.5]: https://github.com/P4Software/Rotula/compare/v0.3.4...v0.3.5
[0.3.4]: https://github.com/P4Software/Rotula/compare/v0.3.3...v0.3.4
[0.3.3]: https://github.com/P4Software/Rotula/compare/v0.3.2...v0.3.3
[0.3.2]: https://github.com/P4Software/Rotula/compare/v0.3.1...v0.3.2
[0.3.1]: https://github.com/P4Software/Rotula/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/P4Software/Rotula/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/P4Software/Rotula/compare/v0.1.2...v0.2.0
[0.1.2]: https://github.com/P4Software/Rotula/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/P4Software/Rotula/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/P4Software/Rotula/releases/tag/v0.1.0
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<Company>P4 Software</Company>
<Product>Rotula</Product>
<!-- Single source of truth for the release version: build-installer.ps1 reads it, CI overrides it from the tag. -->
<Version>0.3.5</Version>
<Version>0.4.0</Version>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/P4Software/Rotula</RepositoryUrl>
</PropertyGroup>
Expand Down
Loading
Loading