![]() |
CSSMedia is a TypeScript / Node.js engine that reads an XML file and a CSS stylesheet and generates a PDF that follows what the CSS specifications say must be done with that stylesheet.
Styling comes from CSS alone. The engine makes no assumptions about element names or attribute semantics, so any well-formed XML can be styled, not just a specific vocabulary like DITA or DocBook. PDF is the only output.
Work in progress. CSSMedia already generates PDF files and is under active development. Support for CSS features is not complete yet, and more is being added.
One article in three languages, formatted by the same stylesheet:
| English | Arabic | Japanese |
|---|---|---|
![]() |
![]() |
![]() |
After installing CSSMedia, run it on the English sample from the samples folder:
cd samples
cssmedia -x xml_gap/xml_gap.xml -c xml_gap.css -p xml_gap/xml_gap.pdfThe command prints a warning about the DocBook DTD and generates xml_gap/xml_gap.pdf. See xml_gap for the explanation and for the stylesheet techniques that the sample shows.
An XML document describes content, not pages. To publish it as a PDF you have to say how it looks on paper: page size and margins, fonts, headers and footers, page numbers, line breaking for each language.
The traditional way to do that is a two-step pipeline: an XSLT stylesheet transforms the XML into XSL-FO, and an FO processor renders the result. Both steps need specialized knowledge, and the stylesheets are usually written for one XML vocabulary, so each new vocabulary needs new stylesheets.
CSSMedia replaces that pipeline with a single CSS stylesheet applied directly to the XML. This is what changes:
- The design is written in CSS. A CSS rule says how an element looks. There are no templates, modes or XPath expressions to write, and no intermediate XSL-FO document to produce and debug. If you know XSL-FO, you can reuse that knowledge: many XSL-FO properties, such as
font-family,text-alignandpadding, are also CSS properties. - The XML is styled directly. The source document is not converted to another format first, so the structure of the output follows the structure of the document.
- One engine for any XML vocabulary. The engine does not assume anything about element names or attributes. Each vocabulary needs only its own stylesheet.
- One stylesheet for many languages. The same stylesheet can format a document in several languages and scripts, as the xml_gap samples show.
- Behavior defined by specifications. Features are implemented from the CSS specifications, so what a property does is documented by the specification that defines it.
- Multiple languages and scripts: documents can be in any language, including right-to-left scripts such as Arabic and CJK languages such as Japanese. The xml_gap samples show the same article in English, Arabic, Japanese and Russian, all styled by one stylesheet.
- Hyphenation by language: with
hyphens: auto, words are hyphenated using the patterns of the language declared in the document by usingxml:lang. CSSMedia includes 75 hyphenation pattern sets, which cover many languages and some of their variants, such asen-usanden-gb. The swordfish_hyphens.pdf sample is generated with hyphenation turned on. - CSS-only styling: the XML is styled by CSS alone. Selector matching, specificity, cascade, inheritance and value resolution are CSSMedia's own code;
css-treeis used only to parse CSS syntax. - Fonts: OpenType, TrueType, collections, WOFF and WOFF 2.0 files, loaded from
@font-facerules or a fonts folder, plus the 14 standard PDF fonts. - Images: readers for PNG, JPEG, JPEG 2000 and WebP, written in CSSMedia's own code.
- SVG and MathML: SVG and MathML found in a document are handled as images.
- Dependencies: XML is parsed with TypesXML, language tags are handled with TypesBCP47, CSS syntax is parsed with
css-tree, andpdfnativeprovides font data and the shaping of Indic and other complex scripts.
The samples folder has XML files, stylesheets and the fonts they use. The stylesheets load the fonts from samples/fonts with @font-face rules.
A DocBook article in four languages, all formatted with xml_gap.css. The Arabic, Japanese and Russian files were translated automatically with DeepL. The goal of the samples is to test typesetting, not to provide perfect localization.
| Language | XML | |
|---|---|---|
| English | xml_gap.xml | xml_gap.pdf |
| Arabic | xml_gap_ar.xml | xml_gap_ar.pdf |
| Japanese | xml_gap_ja.xml | xml_gap_ja.pdf |
| Russian | xml_gap_ru.xml | xml_gap_ru.pdf |
The files declare a DocBook DTD. Without a catalog, CSSMedia prints the warning External subset not found: docbook/docbookx.dtd and still generates the PDF. To avoid the warning, pass a catalog that resolves the DTD with -g, such as the one in TypesBook.
These are the techniques that the stylesheet uses:
| Result | CSS |
|---|---|
| Local fonts | @font-face rules with src: url("./fonts/NotoSans-Regular.ttf") |
| Page size, margins and a footer with the document title and the page number | @page with @bottom-left and @bottom-right, string-set: doc-title content() on the title and counter(page) |
| A different font for each language | selectors such as article[lang|="ar"] and article[lang|="ja"] |
| Right-to-left text in Arabic | direction: rtl |
| Stricter line breaking in Japanese | line-break: strict |
| Links from an XML attribute | -cssmedia-link: attr(url) on ulink |
| An image from an XML attribute | content: attr(fileref url) on imagedata |
| The photo at the start side of the text, on the right in Arabic and on the left in the other languages | float: inline-start on mediaobject |
| An arrow after each link | ulink::after { content: " \2197"; } |
| Code listings in a monospace font | font-family: "Noto Sans Mono", monospace with white-space: pre |
The User Guide of Swordfish, a real product, originally written in DITA, merged into one XML file in two ways. Each merged file needs a stylesheet written for its structure.
| Merged with | XML | Stylesheet | |
|---|---|---|---|
| TypesDitac | swordfish.xml | ditac.css | swordfish.pdf |
| TypesDitac | swordfish.xml | ditac_hyphens.css | swordfish_hyphens.pdf |
| DITA Open Toolkit | swordfish_dita-ot.xml | dita-ot.css | swordfish_dita-ot.pdf |
Differences between the stylesheets:
| Stylesheet | Input | Hyphenation |
|---|---|---|
ditac.css |
TypesDitac file | none |
ditac_hyphens.css |
TypesDitac file | hyphens: auto on the root element, with the patterns of each language |
dita-ot.css |
DITA-OT file: the map comes first, then the topics | none |
CSSMedia reads one XML file, so a DITA map and its topics have to be merged first. The commands below use the map of the CSSMedia manual, manual/CSSMedia.ditamap, as the example.
With TypesDitac, use the --merge option and name the output file with -o:
typesditac CSSMedia.ditamap --merge -o CSSMedia_MERGED.xmlWith the DITA Open Toolkit, the PDF transformation writes the merged file to its temporary directory, and DITA-OT deletes that directory after the build. To keep the merged file, set clean.temp to no:
dita --input=CSSMedia.ditamap --format=pdf --output=out --temp=temp --clean.temp=noThe merged file is temp/CSSMedia_MERGED.xml: the name of the map without its extension, followed by _MERGED.xml. The --temp option is optional and makes the location predictable.
Run cssmedia and the program prints this help text:
Usage: cssmedia -x <file> -c <file> -p <file> [-f <fontsFolder>] [-g <catalog>]
-x, --xml <file> XML file to format
-c, --css <file> CSS stylesheet to apply
-p, --pdf <file> PDF file to write
-f, --fonts <folder> (optional) alternative fonts folder
-g, --catalog <file> (optional) OASIS catalog to use when parsing XML
Example:
cssmedia -x document.xml -c document.css -p document.pdfUse -g to pass a custom OASIS catalog, so that the XML file is read with its associated grammars:
cssmedia -x document.xml -c document.css -p document.pdf -g catalog.xmlWarnings are printed to stderr. When processing fails, the program prints the error and exits with code 1; wrong arguments exit with code 2.
Fonts come from the @font-face rules of the stylesheet, then from the folder given with -f. CSSMedia does not search the fonts installed on the computer. The 14 standard PDF fonts are always available under the family names Times, Helvetica, Courier, Symbol and ZapfDingbats. A declared family or a family in the -f folder with the same name takes precedence.
CSSMedia loads OpenType and TrueType (.otf, .ttf), OpenType collections (.otc, .ttc), WOFF 1.0 (.woff) and WOFF 2.0 (.woff2) fonts. The User Manual describes the @font-face descriptors, the format() and tech() values that are supported, and the sources that are skipped.
PdfGenerator.generate() writes the PDF. The constructor takes the optional font folder and the optional XML catalog, and the warnings of the last run are in warnings:
const generator: PdfGenerator = new PdfGenerator("/path/to/fonts");
generator.generate("document.xml", "document.css", "document.pdf");
generator.warnings.forEach((warning: string): void => console.error(warning));The package is the tarball cssmedia-x.x.x.tgz in the root of this repository.
npm install -g file:cssmedia-x.x.x.tgzThe -g option installs the package globally and adds the cssmedia command, which you can run from any folder. To install it locally in a project instead:
npm install file:cssmedia-x.x.x.tgzYou need Node.js 24 or newer and npm.
git clone https://github.com/maxprograms-com/CSSMedia.git
cd CSSMedia
npm install
npm run buildnpm run build compiles ts/ to dist/. The command is then available as:
node ./dist/cssmedia.js [options]The CSSMedia User Guide (PDF) explains how to run CSSMedia and how to write stylesheets for it. It is still a work in progress.
The manual is written in DITA XML and its PDF is generated with CSSMedia itself, so it is also an example of what CSSMedia produces. See manual/README.md for the build instructions.
- TypesXML parses the XML documents that CSSMedia formats.
- TypesBCP47 validates and normalizes the language tags found in documents.
- TypesDitac merges a DITA map and its topics into the single XML file. The User Guide is built by processing the files in
manualwith TypesDitac and CSSMedia. - TypesBook merges a set of DocBook XML documents into a single XML.
Report problems and request features using GitHub Issues. For support, write to tech@maxprograms.com or visit the Maxprograms support page.
ts/: source files, in folders by component (css,fonts,images,layout,mathml,pages,paint,parse,pdf,svg,writer)dist/: compiled JavaScript and declarations (npm run build)manual/: source of the User Guide, written in DITA XMLlicenses/: licenses of the included components
CSSMedia is Copyright © 2026 Maxprograms. See the LICENSE file for the terms of use. License information for all included components is available in the licenses directory.



