Skip to content

Add an option to strip embedded fonts - #49

Merged
SimonCropp merged 1 commit into
mainfrom
strip-embedded-fonts
Oct 9, 2026
Merged

SimonCropp merged 1 commit into
mainfrom
strip-embedded-fonts

Conversation

@SimonCropp

Copy link
Copy Markdown
Owner

Problem

A producer embeds a subset of whichever copy of a font the machine has installed. A developer machine with Arial 7.06 and a build agent with Arial 7.01 therefore render the same document to different bytes. The two font programs draw the same glyphs, but they differ in content and in length, and being compressed they hold nothing that could be zeroed. Aspose.Words, Aspose.Cells and Aspose.PDF all behave this way.

Change

Every Normalize / NormalizeAsync overload gains a counterpart taking bool stripEmbeddedFonts. They are new overloads rather than an optional parameter, so this is not a binary break. It is off by default because it is lossy: a viewer opening the result substitutes fonts of its own.

For each font descriptor entry /FontFile, /FontFile2 or /FontFile3:

  • The entry is removed, leaving the font named but not embedded.
  • The data of the font stream is removed.
  • /Length, /Length1, /Length2 and /Length3 are restated as 0, in the dictionary or in the object an indirect one refers to.
  • An object that holds nothing but the stream's length, and that no reference names, is restated as 0 too. Aspose.PDF leaves one behind even when the dictionary states the length directly.

The stream sits in the middle of the body, so the cross-reference section is repaired:

  • A classic table has its offsets rewritten in place.
  • A cross-reference stream (what Aspose.PDF writes) is rewritten uncompressed. Recompressing it would make the output depend on the deflate implementation of the runtime.

Fonts are left embedded when the document cannot be safely rewritten: an incremental update, a hybrid table (/XRefStm), a cross-reference stream with a predictor (/DecodeParms) or a filter other than /FlateDecode, or a font descriptor held in an /ObjStm.

The pass runs last, so every other pass sees the document as the producer wrote it. Each removed entry is reported under its key (/FontFile2).

Version bumped to 2.2.0.

Testing

  • 13 new tests in StripEmbeddedFontsTests.cs, each run against both a classic table and a cross-reference stream where that matters: differing font programs collapse to the same output, the offsets are read back and checked against the objects they point at, the pass is idempotent, and an incrementally updated document is left alone.
  • Run against real Aspose output from a consuming project: 61 documents produced on a build agent and the same 61 produced on a developer machine, which differed only in their embedded fonts, all normalize to byte-identical output.

A producer embeds a subset of whichever copy of a font the machine has installed, so two machines with different versions of one font render the same document to different bytes. stripEmbeddedFonts removes the font programs and the descriptor entries pointing at them, and repairs the cross-reference table or stream.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant