Photoshop, email, calendars, PostScript and OneNote: five formats, five origins
Rafael
X-Ray, part 4 of 5. After the family, the shared rules and Office, this post covers the five packages in
x-ray-others. Part 5 is OCR.
A content extractor reads what a document says. Some files need more than that: the layer tree of a Photoshop file, the attendees and recurrence rule of a calendar invitation, the attachments of an Outlook message. Each of those formats got its own package, in one repository with one build skeleton and one pipeline, and each has a different origin. That matters for trust, so this post says for each one what it started from and what the check against that starting point looks like.
The repository's history starts on 16 October. The commits in it were written by agents and merged by me, and the posts on Office and OCR say more about how that works.
X-Ray.Psd: from a C++ image editor
X-Ray.Psd reads PSD and PSB files in the common
bit depths and colour modes. It composites layers, renders to PNG and JPEG, extracts text and gives
typed access to resources and effects.
var document = PsdDocument.Load("poster.psd");
document.Render().SavePng("poster.png");
foreach (var item in document.ExtractText().Items)
Console.WriteLine($"{item.Kind}: {item.Source}: {item.Text}");
It is a port of the PSD engine of Patchy, a C++/Qt image editor. Patchy's C++ stays in that repository as the behavioural reference. Some features have no counterpart in Patchy: knockout, shadow and glow contours, several adjustment layers (Color Balance, Photo Filter, Vibrance, Channel Mixer, Gradient Map and others), duotone and streaming output for PSB files past 2 GB.
How it is checked: for every fixture, the layer compositor's output is compared with the merged image Photoshop stored in the file, and with Photoshop reference renderings that came with Patchy's fixtures. Test files from psd-tools covered the layer comps and legacy effects, and one file from ag-psd is the only Photoshop file in the corpus with type on a path. Every fixture also goes through truncation and mutation tests.
X-Ray.Content uses this package for PSD files, through its structural API only. It never
decodes pixels, which is what keeps a multi-gigabyte PSB cheap to index.
X-Ray.Email: from Aspose.Email FOSS
X-Ray.Email reads and writes Outlook MSG and
compound files, parses EML and MIME, converts between the two, builds an HTML preview of a
message and saves its attachments as files.
It started as a derivative of Aspose's open-source Aspose.Email FOSS library. Since then the code has moved away from it. About forty parsing and writing bugs have been fixed, so the original is no longer the reference. The specifications are: the Microsoft MSG and compound-file formats and the MIME RFCs. The MIME stack, the preview engine and the RTF and HTML sanitiser are new, and they were written for Curiosity's file-extraction helper.
How it is checked: there are 110 fixtures: 15 from the xberg corpus, 25 and 28 from two
third-party test sets, and 42 of our own. Expected values come from tools that share
no code with ours: Python's email module for EML and
extract_msg 0.56.1 for MSG. Every fixture is
round-tripped (MSG to MSG, EML to EML, EML to MSG, MSG to EML), and a robustness suite mutates
them.
X-Ray.Ics: from two RFCs, with no upstream
X-Ray.Ics is the only package in the family with
no upstream code. It reads iCalendar (RFC 5545, with the basics of iTIP) and vCalendar 1.0,
expands recurrences, writes calendars, and extracts an appointment to HTML, text and Markdown.
IcsExtractResult appointment = IcsExtractor.ExtractFile("meeting.ics");
Console.WriteLine(appointment.Summary);
string html = appointment.ToHtml();
Recurrence is the hard part. A post on pwy.io about expanding recurrence rules quotes the RFC's table of how each rule part behaves as a filter or as an expansion, and says that its "apparent complexity is what causes libraries to implement iCal features just partially". We wrote the reader from the specifications and the shape of real exports, and the fixture corpus carries the weight.
How it is checked: there are 434 fixtures. 198 come from the
icalendar Python library (BSD-2-Clause), 150 from
ical.net, and 86 are ours: alarms, encodings (UTF-8
with a BOM, UTF-16 in both byte orders, Windows-1252), recurrence cases, daylight-saving
crossings, malformed files and exports in the style of Google, Apple and Outlook. Parsing
expectations come from icalendar 7.3.0, and recurrence expectations from
recurring-ical-events 3.8.2, which uses dateutil. Where we deliberately differ, a table of
overrides records each difference with its reason: quoted-printable edge cases, empty parameters,
damaged base64, and vCalendar 1.0, which the reference cannot read at all. Known gaps: a PERIOD
value in RDATE contributes only its start, RANGE=THISANDFUTURE is not applied, and Windows
time-zone names come from a built-in table of the common ones.
X-Ray.PS: from Aspose.Page FOSS
X-Ray.PS interprets PostScript and EPS, reads and
writes XPS, and converts both to PDF and to PNG, JPEG, BMP and TIFF. It is a port of
Aspose.Page FOSS for Python, and each C# file cites the Python file it was read from.
The port reproduces Python's behaviour where the output depends on it, so that programs that fail
in the original fail the same way here. It also fixes one thing: the original's XPS path-data parser can loop
forever on malformed C or S data, and the port breaks out.
How it is checked: a parity harness runs the Python original and the port over the same inputs: 583 PostScript and EPS files and 62 XPS files.
| Check | PS and EPS | XPS |
|---|---|---|
| Success or failure agrees | 583 of 583 | 62 of 62 |
| Render model identical | 400 of 400 | 56 of 57 |
| PDF pages, sizes and text | 400 of 400 | 57 of 57 |
| Raster output against the original's | identical for 398 of 400 | 74 of 85 pages identical |
X-Ray.OneNote: from Aspose.Note FOSS
X-Ray.OneNote reads .one section files
(desktop and online packages) and, as far as the format allows, notebook tables of contents, and
exports pages to PDF and HTML. It is a port of Aspose.Note FOSS for Python, with its
tests and format notes beside it and the MS-ONESTORE and MS-ONE specifications as the second
reference.
Porting turned up places where the original is wrong, and the goldens now say so. It reads a one-byte count as part of the first float of a table's column widths. It reports a completion time in 1980 for note tags. Five groups of fields in the golden files therefore hold this package's values, and the repository lists which. Our own PDF writer replaced the original's use of ReportLab, and HTML export is new.
How it is checked: all 12 fixtures match the Python goldens field for field, page history included, and their PDFs match the Python manifests. A second, independent parser (written in Rust) was used only as an oracle: its test suite supplied 32 section files and 12 table-of-contents files, and all 32 sections load here with the same name, page count, order, levels and colour.
Who we thank
Aspose, for the open-source Email, Page and Note libraries that the three ports start from. Thank you.
Where this stops
Where we diverge from an original, the repository says so and says why.
Further reading
- Marching Events: What does iCalendar have to do with ray marching?, pwy.io. An approach to expanding recurrence rules, and a clear account of why the RFC's table makes partial implementations common. HN discussion.
- Choosing a package, which says which of these to use for which file.
Read next
Articles on context graphs, enterprise search and industrial AI