mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 08:52:32 +00:00
The outlook feature adds MailCraft's reader of PidTagRtfCompressed ([MS-OXRTFCP]) and of the text and encapsulated HTML ([MS-OXRTFEX]) of the RTF inside, from crates/pst/src/rtf.rs at a44a450. Font charsets use this crate's charset_code_page table; code pages are decoded by craft-mime. Co-authored-by: Martin Ball <98639962+benandez@users.noreply.github.com>
99 lines
5.3 KiB
Markdown
99 lines
5.3 KiB
Markdown
# craft-rtf
|
|
|
|
Rich Text Format, read and written. It replaces the RTF code each Craft app carried: the
|
|
tokenizer, code pages, `\u` handling, dates and escaping come from WordCraft's reader and
|
|
writer; the small model covers what DesignCraft's text import, DeckCraft's outline import and
|
|
export and PdfCraft's export read and write.
|
|
|
|
```rust
|
|
use craft_rtf::{Block, CharFmt, Document, Para, Run, read, write};
|
|
|
|
let bold = CharFmt { bold: true, ..CharFmt::default() };
|
|
let doc = Document {
|
|
blocks: vec![Block::Para(Para {
|
|
runs: vec![Run { text: "Plan {Q3} ".into(), fmt: bold }, Run { text: "café 😀".into(), fmt: CharFmt::default() }],
|
|
outline_level: Some(0),
|
|
..Para::default()
|
|
})],
|
|
pictures_left_out: 0,
|
|
};
|
|
let rtf = write(&doc, "Liberation Sans");
|
|
assert!(rtf.contains(r"\outlinelevel0 {\b Plan \{Q3\} }caf\u233?"));
|
|
assert_eq!(read(rtf.as_bytes()).unwrap(), doc);
|
|
```
|
|
|
|
## Layers
|
|
|
|
| Layer | What it is | For |
|
|
| --- | --- | --- |
|
|
| `Tokens` | The tokens of a file: `{`, `}`, control words with their parameter, `\*`, `\'hh`, control symbols, text runs, `\bin` data. | Any reader. |
|
|
| `TextDecoder`, `codepage` | 8-bit text in a code page, `\uN` and its fallback characters. | Any reader. |
|
|
| `esc`, `esc_font`, `date_words`, `iso_to_dttm` | Escaping and dates. | Any writer. |
|
|
| `read`, `write`, `Document` | A file to and from a small model. | An app that needs text with basic formatting. |
|
|
|
|
A reader that keeps more than the model holds (styles, lists, fields, notes, tracked changes,
|
|
pictures) uses the first two layers and its own interpretation of the control words.
|
|
|
|
## Text
|
|
|
|
- **Code pages.** 8-bit text is in the current font's code page (`\fcharset`, `\cpg`), else
|
|
the default font's (`\deff`), else the document's (`\ansicpg`, `\mac`, `\pc`, `\pca`), else
|
|
Windows-1252. Single-byte: Windows 874 and 1250 to 1258, DOS 437, 737, 850, 852, 857, 866,
|
|
Mac OS Roman. Multibyte: 932 (Shift-JIS), 936 (GBK), 949 (UHC), 950 (Big5), streamed through
|
|
one decoder so a lead byte and its trail byte decode together whatever falls between them.
|
|
Raw bytes above 0x7F are text in the code page, as `\'hh` is. They are not read as UTF-8.
|
|
- **`\uN`.** One UTF-16 code unit, taken modulo 65,536. A high surrogate pairs only with a low
|
|
surrogate in the very next `\u`; a surrogate that does not pair is U+FFFD, as in Word. The
|
|
`\ucN` characters after a `\u` are its fallback and are skipped: a text byte, a `\'hh`, a
|
|
control word and a control symbol each count as one, a following `\u` does not, and the
|
|
skipping ends with the group.
|
|
- **Malformed input.** `\'` without two hex digits is dropped with the hex digits it has; a
|
|
`}` or `\` after it keeps its meaning. A control word is at most 32 letters and a parameter
|
|
at most 10 digits. A parameter outside `i32` is clamped.
|
|
|
|
## The model
|
|
|
|
`Document { blocks, pictures_left_out }`, `Block::{Para, Table}`, `Para { runs, outline_level,
|
|
left_indent, right_indent, first_line_indent, space_before, space_after, align }`, `Run { text, fmt }`, `CharFmt { bold, italic, underline, strike,
|
|
vert_align, half_points, color, font }`, `Table { rows: Vec<Vec<Cell>> }`, `Cell { right, paras }`.
|
|
|
|
In `Run::text`, a tab is `\t`, and `LINE_BREAK` (`\n`), `PAGE_BREAK` (U+000C) and
|
|
`COLUMN_BREAK` (U+000E) are `\line`, `\page` and `\column`. Lengths are in twips and sizes in
|
|
half-points, as the file has them.
|
|
|
|
`read` leaves out headers, footers, footnotes, comments, field instructions (a field's result
|
|
is kept), list labels (`\listtext`, `\pntext`), the stylesheet, pictures and objects (counted),
|
|
and every destination marked `\*`. Paragraph properties end with their group, as character
|
|
properties do. A trailing `\par` does not leave an empty last paragraph; empty paragraphs
|
|
elsewhere are kept. `\sect` ends a paragraph.
|
|
|
|
### Limits
|
|
|
|
| Limit | Value | Past it |
|
|
| --- | --- | --- |
|
|
| `MAX_GROUPS` | 512 nested groups | deeper groups are read in the state of the group around them |
|
|
| `MAX_FONTS` | 4096 font table entries | later fonts are not kept |
|
|
| `MAX_COLORS` | 4096 colour table entries | later colours are not kept |
|
|
| `MAX_FONT_NAME` | 256 bytes | the font is not kept |
|
|
|
|
The reader does not recurse, and what it returns is never larger than a small multiple of the
|
|
file: each character of text costs the file at least one byte.
|
|
|
|
## Outlook bodies (feature `outlook`)
|
|
|
|
`outlook::decompress_rtf` undoes the compression of `PidTagRtfCompressed` ([MS-OXRTFCP]: `LZFu`
|
|
with its CRC checked, or `MELA` stored as is), never allocating past the caller's cap.
|
|
`outlook::rtf_text` gives the text a mail body shows (font charsets and `\ansicpg` decoded through
|
|
`craft_mime::charset`, `\uN` with its fallback skipped, destinations dropped), and
|
|
`outlook::rtf_html` the HTML of an encapsulated-HTML body ([MS-OXRTFEX], `\fromhtml1`). Groups nest
|
|
at most `MAX_RTF_DEPTH` (256) deep. From MailCraft `crates/pst/src/rtf.rs` at
|
|
[a44a450](https://github.com/UCS-Care-Group/mailcraft/commit/a44a45013d1c6396229c40ab60172d1882b368d5),
|
|
by Martin Ball and the MailCraft contributors.
|
|
|
|
## Not here yet
|
|
|
|
WordCraft's full reader and writer (styles, lists, fields, bookmarks, pictures, notes,
|
|
comments, tracked changes, sections, headers and footers) are written against WordCraft's own
|
|
document model and stay there until that model has a neutral form. They can already sit on
|
|
`Tokens`, `TextDecoder`, `esc` and the date functions.
|