Files
craft-libs/crates/rtf/src/lib.rs
T
Dylan M. TaylorandSimon Koch-Sultan 9ab7768771 Add craft-rtf: RTF tokenizer, code pages, and a reader and writer for a small model (#890)
* Add craft-rtf: RTF tokenizer, text decoding, and a reader and writer for a small model

The tokenizing rules, code pages, multibyte decoding, dates and escaping come
from WordCraft's RTF reader and writer. The model (paragraphs with outline
level and indent, character formatting, tables) covers what DesignCraft's text
import, DeckCraft's outline import and export and PdfCraft's export use.

An unpaired UTF-16 surrogate in \u reads as U+FFFD and a high surrogate pairs
only with the very next \u, as storytold/wordcraft#6105 describes for Word.

* craft-rtf: a symbol font's bytes are its own codes

A font with \fcharset2 (Symbol, Wingdings) has no letters: a byte from 0x80
reads as U+F000 + the byte instead of a letter of the document's code page.
The symbol page applies to body text only, a symbol default font's page does
not apply to text in a font chosen with \f, \plain makes the default font's
page current again, and a code page an entry names with \cpg is kept.

Ported from storytold/wordcraft#6014.

* craft-rtf: alignment, indents, spacing, superscript and subscript; NUL is not text

The model and both directions gain what DesignCraft's RTF export writes:
\ql \qc \qr \qj, \ri, \fi, \sb, \sa, \super, \sub, \nosupersub.
A \u0 or \'00 no longer puts a NUL into the text.

---------

Co-authored-by: Simon Koch-Sultan <126534658+scks1@users.noreply.github.com>
2026-11-11 17:26:57 -05:00

46 lines
1.9 KiB
Rust

#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
//! Rich Text Format.
//!
//! Three layers, each usable alone:
//!
//! - [`Tokens`]: the tokens of a file (groups, control words, `\'hh`, text runs, `\bin` data).
//! - [`TextDecoder`] and [`codepage`]: text in the file's code pages, `\uN` with its fallback
//! characters and surrogate pairs. [`esc`] goes the other way. [`dttm_to_iso`],
//! [`iso_to_dttm`], [`date_words`] and [`iso_date`] are RTF's two date forms.
//! - [`read`] and [`write`]: a file to and from a small neutral model ([`Document`]):
//! paragraphs with outline level, alignment, indents and spacing, runs with bold, italic,
//! underline, strikethrough, superscript and subscript, size, colour and font, and tables.
//!
//! A reader that keeps more than the model holds (styles, lists, fields, notes, pictures) is
//! built on the first two layers.
//!
//! ```
//! use craft_rtf::{Block, read, write};
//!
//! let doc = read(br"{\rtf1\ansi\pard\outlinelevel0 Caf\'e9 {\b \u8364?5}\par}")?;
//! let Some(Block::Para(p)) = doc.blocks.first() else { return Ok(()) };
//! assert_eq!((p.text().as_str(), p.outline_level), ("Café €5", Some(0)));
//! assert!(p.runs[1].fmt.bold);
//! assert_eq!(read(write(&doc, "Helvetica").as_bytes())?, doc);
//! # Ok::<(), craft_rtf::NotRtf>(())
//! ```
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeExamples;
pub mod codepage;
mod date;
mod lex;
mod model;
mod read;
mod text;
mod write;
pub use date::{date_words, dttm_to_iso, iso_date, iso_to_dttm};
pub use lex::{Token, Tokens, is_rtf};
pub use model::{Align, Block, COLUMN_BREAK, Cell, CharFmt, Document, LINE_BREAK, PAGE_BREAK, Para, Run, Table, VertAlign};
pub use read::{MAX_COLORS, MAX_FONT_NAME, MAX_FONTS, MAX_GROUPS, NotRtf, read};
pub use text::TextDecoder;
pub use write::{esc, esc_font, push_esc, write};