This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Flying Saucer is a pure-Java CSS 2.1 renderer that turns well-formed XML/XHTML into Java2D, PDF, or SWT output. It is published to Maven Central under org.xhtmlrenderer:*. Java 21+ is required (CI also runs on JDK 25).
mvn install # full multi-module build (also runs tests)
mvn -B package # what CI runs
mvn test # unit tests across all modules
mvn -pl flying-saucer-pdf test # tests for one module (use -am to also build deps)
mvn -pl flying-saucer-pdf test -Dtest=ITextRendererTest # single class
mvn -pl flying-saucer-pdf test -Dtest=ITextRendererTest#renderPdf # single method
mvn versions:set -DnewVersion=X.Y.Z # bump version across all poms (release flow)
Surefire only picks up tests under org/** (see pom.xml). JUnit 4 (junit:junit) is banned by the enforcer plugin — write tests in JUnit 5 (Jupiter) with AssertJ; PDF assertions use com.codeborne:pdf-test.
- Use AssertJ's type-specific matchers instead of unwrapping. E.g. for
AtomicInteger:assertThat(counter).hasValue(0)— neverassertThat(counter.get()).isZero(). Same idea forOptional,Path,File, collections, etc.: assert on the wrapper, not on a manually extracted value. - Static-import common helpers to keep tests terse:
org.assertj.core.api.Assertions.assertThat,java.util.Objects.requireNonNull, and any project test utility (e.g.TestUtils.printFile). Avoid qualifying these inline. - Prefer Mockito's no-arg
mock()overmock(SomeClass.class)— the target type is inferred from the variable/field it's assigned to, e.g.private final CssContext ctx = mock();instead ofmock(CssContext.class).
maven-compiler-pluginruns Error Prone as a-Xplugin. A handful of checks are disabled globally (MissingSummary,JdkObsolete,ReferenceEquality,OperatorPrecedence) andLexer.javais excluded entirely. If you hit unexpected compile errors, suspect Error Prone before suspecting javac.flying-saucer-coreregeneratesorg/xhtmlrenderer/css/parser/Lexer.javafromLexer.flexvia the JFlex plugin duringprocess-resources. Do not hand-editLexer.java— changeLexer.flexand rebuild.- Nullness is annotated with JSpecify (
@Nullable,@NonNull); honor those when changing signatures. Indent is 4 spaces (2 for XML), UTF-8 (see.editorconfig). - Prefer immutability: build immutable objects instead of mutating a collection into shape (
List.of(horizontal, vertical)instead ofnew ArrayList<>(2); add(horizontal); add(vertical), orvalues.stream().map(...).toList()instead of populating anArrayListin a loop — reach for a mutable collection only when the final size/contents genuinely can't be expressed as a single literal or stream pipeline); preferprivate finalfields set once (in the constructor or at declaration) over fields reassigned later. - Prefer record-style accessor names (
age()) over JavaBean-style (getAge()) in new code, matching Java'srecordconvention — but don't rename accessors on existing non-record classes just to match this style.
The build is a Maven multi-module reactor; the publishable artifacts are:
flying-saucer-core— layout engine, CSS parser, Java2D output, Swing renderer (XHTMLPanel,Graphics2DRenderer,ImageRenderer).flying-saucer-pdf— PDF output via OpenPDF (LibrePDF fork of iText 2.x). Entry points:ITextRenderer,PDFRenderer,Html2Pdf. Includes Batik for SVG.flying-saucer-pdf-osgi— OSGi bundle wrapper aroundflying-saucer-pdf.flying-saucer-swt— SWT output device.flying-saucer-log4j— optional Log4j logging adapter.flying-saucer-fop— Apache FOP-based font/glyph support.flying-saucer-examples— runnable demos (e.g.org.xhtmlrenderer.demo.browser.BrowserStartup); not published.
Everything but the OpenPDF fork is exclusion-managed in the parent pom.xml; legacy XML libs (xml-apis, xerces, xalan) are explicitly excluded from Batik to avoid JPMS/java.xml conflicts.
org.xhtmlrenderer in flying-saucer-core is organized as a layered renderer. Key packages:
css/— CSS 2.1 implementation.parser/(JFlex-generated lexer + hand-written parser),sheet/(stylesheet model),newmatch/(selector matching),style/(computed style cascade),constants/,value/.extend/— the SPI.OutputDevice,FontResolver,TextRenderer,UserAgentCallback,ReplacedElementFactory,NamespaceHandler,FSImage. Each output backend (Java2D, PDF, SWT) is an implementation of these interfaces — that is the seam for adding a new renderer.layout/— block/inline layout, line breaking, floats, page breaks.SharedContextcarries config that lives across the whole document;LayoutContext/RenderingContextare per-pass.BoxBuilderturns the styled DOM into a box tree.render/— the box tree itself (Box,BlockBox,InlineBox,LineBox,PageBox,Layer) and painting helpers (BorderPainter,TextDecoration).newtable/— CSS table layout (separate from generic block layout for historical reasons).swing/— Java2DOutputDeviceand the Swing user agent / image loader.simple/— convenience facades (XHTMLPanel,Graphics2DRenderer,ImageRenderer).context/,resource/,event/,util/,debug/— supporting infrastructure.
PDF output (flying-saucer-pdf, package org.xhtmlrenderer.pdf) plugs into the same SPI: ITextOutputDevice, ITextFontResolver, ITextUserAgent, ITextReplacedElementFactory. ITextRenderer is the orchestration entry point.
.github/workflows/maven.ymlrunsmvn -B packageon JDK 21 and 25, then auto-merges Dependabot PRs that pass build (minor versions only, rebase merge).- Dependabot PRs land frequently — keep version bumps confined to
pom.xmlproperties (<properties>block in the parent), never inline a version in a child module.
See CONTRIBUTING.md. Summary: bump version with mvn versions:set, commit, tag vX.Y.Z, mvn clean deploy (signs with GPG and publishes via central-publishing-maven-plugin with autoPublish=true), then bump to next -SNAPSHOT.