Skip to content

[Feature]: Provide a way to clear or replace registered fonts #163

Description

@jeff377

MiniPdf implementation

.NET

Problem or use case

MiniPdf.RegisterFont(string name, byte[] fontData) appends to a process-wide static
list. There is no public or internal way to remove, replace, or clear entries.

MiniPdf is a public static class, so there is no instance to dispose and no scope in
which registrations can be reset.

MiniPdf.cs:110:

private static readonly List<(string Name, byte[] Data)> _registeredFonts = new();

MiniPdf.cs:118:

public static void RegisterFont(string name, byte[] fontData)
{
    ...
    lock (_registeredFonts)
        _registeredFonts.Add((name, fontData));
}

A grep over src/ finds no Clear, Remove, Unregister, or replace path, public or
internal. Every conversion reads the full list, at PdfWriter.cs:173 and
PdfWriter.cs:2774, so the per-conversion font-loading cost grows with the number of
registrations for the lifetime of the process.

Consequences for library hosts:

  • Registrations cannot be released. A long-running host that registers fonts per request
    or per tenant accumulates them; CJK fonts are tens of MB each.
  • A font cannot be replaced. Registering the same name again adds a second entry rather
    than overriding the first, and both entries are loaded.
  • Font sets cannot be swapped between conversions, for example when different documents
    require different font sets.

The XML documentation names Blazor WASM as a target scenario, where a long-lived process
is the normal case.

The CLI is unaffected because it is a one-shot process.

Proposed solution

Either of the following would resolve it; the choice is a design decision for
maintainers:

  1. Add MiniPdf.ClearRegisteredFonts(), and possibly
    MiniPdf.UnregisterFont(string name).
  2. Make RegisterFont key on name, so registering an existing name replaces it. This
    changes existing behavior and may not be desirable.

Option 1 is additive and does not change current behavior.

Affected area

MiniPdf library API

MiniPdf version or baseline

main @ 71c44aa6

Example usage

// A long-running host that serves documents needing different font sets.
MiniPdf.RegisterFont("NotoSansTC", tcBytes);
MiniPdf.ConvertToPdf(inputA, outputA);

// Intent: use only the JP font for the next document.
// There is currently no way to express this.
// MiniPdf.ClearRegisteredFonts();   // does not exist
MiniPdf.RegisterFont("NotoSansJP", jpBytes);
MiniPdf.ConvertToPdf(inputB, outputB);   // both fonts are still loaded

// Intent: replace the TC font with a different build.
MiniPdf.RegisterFont("NotoSansTC", otherTcBytes);   // adds a second entry

Sample files or references

Measured on macOS arm64, main @ 71c44aa6, .NET SDK 9.0.317.

One point worth stating explicitly, so the scope is not overstated: registering the same
font data twice under different names does not duplicate it in the output. Converting
tests/Issue_Files/docx/Issue79_FilledContract.docx with --fonts pointing at a
directory holding two copies of the same TTC produced 579,885 bytes with a single
embedded font, against 579,888 bytes for a single copy. Only slots actually used are
embedded. The cost is in loading and retention, not in output size.

I have not measured process memory growth over a long-running host; the statements above
are from reading the code and from the conversion measurements described.

Alternatives considered

  • Restarting the process to release registrations. Not viable for a server or for Blazor
    WASM.
  • Registering the union of all fonts once at startup. Workable when the set is known and
    small, but it loads every font on every conversion and does not allow replacement.

Please close this if the current lifetime is intentional; I could not tell from the code
or history whether it is a deliberate design choice.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions