You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The indirect reference of the font dictionary a glyph's font came from. Uniquely identifies a font even when several subsets share a name. null for inline/synthetic fonts.
Returns the decoded XMP bytes as ReadOnlyMemory<byte> so you can feed your own hardened XmlReader. GetXDocument() now carries a warning: it parses without security settings.
Every IToken now properly overrides Equals(object) / GetHashCode(); ArrayToken and DictionaryToken pre-compute their hash. Tokens are now usable as dictionary/set keys. See breaking change #5.
The tree-walking interpreter is replaced by a compiled Type4Program over a stack-allocated OperandStack: allocation-free and boxing-free evaluation. All the types involved are internal, so no public API change.
DeviceN tint caching, colour-space caching in ResourceStore, span-based GetColor, StackDictionary<> for marked content / shadings / patterns, and caching of scanned tokens, resolved resources and form XObjects.
Plus a batch of parsing / rendering bug fixes (no API change): infinite recursion in GlyphDataTable.ReadCompositeGlyph#1348, out-of-range character codes in Type 3 fonts #1350, value clamping in IndexedColorSpaceDetails#1352, abbreviated key precedence in inline images #1353, infinite recursion resolving default substitute colour spaces #1355, missing /ColorSpace in JPX images #1357, page contents stored as an indirect array in PdfPageBuilder#1358, lost stream position in the brute-force xref scan #1360, unencrypted-metadata handling in DecryptInternal()#1395, the NameToken.GetHashCode() race condition #1392, and stack-depth tracking in CMapParser#1396.
PdfMerger also no longer leaks the PdfDocuments it opens internally, and opens files with FileShare.ReadWrite | FileShare.Delete (#1382, fixes #1234). Streams and byte arrays you pass in are still owned by you and are not closed.
⚠️ Breaking Changes (with migration snippets)
1. ColorSpaceDetails.GetColor now takes a ReadOnlySpan<double> (#1378)
The abstract GetColor(params double[] values) was replaced by GetColor(ReadOnlySpan<double> values). There is no params overload left, so params-style call sites and IReadOnlyList<double> call sites both break.
// BEFORE (v0.1.15) — in a custom colour space:publicoverrideIColorGetColor(paramsdouble[]values){/* ... */}// AFTER (v0.1.16):publicoverrideIColorGetColor(ReadOnlySpan<double>values){/* ... */}
// Call sites// BEFOREvarcolor=colorSpace.GetColor(0.1,0.2,0.3);varcolor2=colorSpace.GetColor(myReadOnlyList.ToArray());// AFTER — an array converts implicitly to ReadOnlySpan<double>,// but the params form and IReadOnlyList<double> do not.varcolor=colorSpace.GetColor([0.1,0.2,0.3]);varcolor2=colorSpace.GetColor(myReadOnlyList.ToArray());
GetRgb(ReadOnlySpan<double>, out double, out double, out double) is unchanged. If you only need RGB, prefer it — it avoids allocating an IColor.
2. IColorSpaceContext colour setters take double[] (#1380)
SetStrokingColor / SetNonStrokingColor changed from IReadOnlyList<double> to double[], so the operands can be stored in the graphics state without a copy.
// AFTER (v0.1.16) — in a custom IColorSpaceContext:publicvoidSetStrokingColor(double[]operands,NameToken?patternName=null){/* ... */}publicvoidSetNonStrokingColor(double[]operands,NameToken?patternName=null){/* ... */}
// Callers holding an IReadOnlyList<double> / List<double>:context.SetNonStrokingColor(operands.ToArray());
3. Colour operations: Operands is now a ReadOnlySpan<double>, and the types are sealed (#1380)
Affects SetStrokeColor (SC), SetNonStrokeColor (sc), SetStrokeColorAdvanced (SCN) and SetNonStrokeColorAdvanced (scn). The ...Advanced constructors also changed from IReadOnlyList<double> to double[].
// BEFORE (v0.1.15)IReadOnlyList<double>values=op.Operands;varcount=op.Operands.Count;varop2=newSetNonStrokeColorAdvanced(myList,patternName);// AFTER (v0.1.16) — Operands is a span: no LINQ, no .Count, and it// cannot be stored in a field or captured in a lambda.ReadOnlySpan<double>values=op.Operands;varcount=op.Operands.Length;varcopy=op.Operands.ToArray();// if you need to keep itvarop2=newSetNonStrokeColorAdvanced(myList.ToArray(),patternName);
Deriving from these four operation classes is no longer possible; implement IGraphicsStateOperation directly instead.
4. IResourceStore — new TryGetXObjectReference member (#1391)
Adding a member to a public interface breaks custom implementers.
// AFTER (v0.1.16) — add to your custom resource store.// Returning false is a valid, behaviour-preserving implementation:// the processor then falls back to reading the XObject stream.publicboolTryGetXObjectReference(NameTokenname,outIndirectReferencereference){reference=default;returnfalse;}
5. Token types are sealed and now use value equality (#1362)
CommentToken, DictionaryToken, EndOfLineToken, IndirectReferenceToken, NameToken and NullToken are now sealed — subclassing them no longer compiles.
// BEFORE (v0.1.15)publicclassMyDictionaryToken:DictionaryToken{/* ... */}// no longer allowed// AFTER (v0.1.16) — compose instead of inheritpublicsealedclassMyDictionaryWrapper{publicDictionaryTokenToken{get;}publicMyDictionaryWrapper(DictionaryTokentoken)=>Token=token;}
Behavioural: every token now overrides Equals(object) and GetHashCode(). Code that relied on reference equality — HashSet<IToken>, Dictionary<IToken, T>, list.Contains(token), Distinct() — will now treat structurally equal tokens as the same entry.
// AFTER (v0.1.16) — if you genuinely need identity semantics:varbyIdentity=newDictionary<IToken,T>(ReferenceEqualityComparer.Instance);varseen=newHashSet<IToken>(ReferenceEqualityComparer.Instance);
6. PdfPageBuilder.ResetColor() no longer restores the whole graphics state (#1385)
SetStrokeColor() / SetTextAndFillColor() used to emit a q (push) and ResetColor() a matching Q (pop), so ResetColor() silently reverted the CTM, clipping path, line width and everything else changed in between — and unbalanced calls corrupted the stack. Now ResetColor() only resets the stroking and non-stroking colours to black (ISO 32000-1 table 52) and leaves the rest of the state alone.
// BEFORE (v0.1.15) — ResetColor() also undid the line width / CTM / clip.page.SetStrokeColor(255,0,0);page.DrawLine(a,b,lineWidth:3);page.ResetColor();// colour AND line width restored// AFTER (v0.1.16) — only the colours are reset.page.SetStrokeColor(255,0,0);page.DrawLine(a,b,lineWidth:3);page.ResetColor();// stroke + fill colour -> black, nothing else
If you were relying on the old save/restore behaviour, wrap the section explicitly:
Related, same PR: DrawRectangle, DrawTriangle and DrawEllipsis used to re-emit SetLineWidth(lineWidth) after drawing instead of restoring the default; they now correctly restore SetLineWidth(1). Shapes drawn after one of these calls without an explicit width will now use width 1.
7. BaseNumberOfColorComponents recurses for DeviceN / Separation, and Separation.BaseType changed (#1376) — behavioural
DeviceNColorSpaceDetails.BaseNumberOfColorComponents and SeparationColorSpaceDetails.BaseNumberOfColorComponents now return AlternateColorSpace.BaseNumberOfColorComponents instead of AlternateColorSpace.NumberOfColorComponents. For a nested alternate space (e.g. Separation over DeviceN over DeviceCMYK, or over an ICCBased), the previous value was wrong. SeparationColorSpaceDetails.BaseType is now the alternate space's Type rather than ColorSpace.Separation.
// No code change needed, but if you sized buffers from these values, re-check:// before: BaseNumberOfColorComponents could report the *immediate* alternate's// component count;// after: it reports the count of the deepest underlying device space.varcomponentsPerSample=colorSpace.BaseNumberOfColorComponents;
8. Indexed colour tables decode through the base colour space (#1369, #1375) — behavioural
Indexed colour-table bytes are now decoded into the base space's native component ranges (ISO 32000-2, 8.6.6.3: min + byte / 255 × (max − min)) instead of being treated as [0, 1] for every base space. Colours from Indexed spaces over Lab — and any other space whose components are not in [0, 1] — change; they were previously wrong. Combined with the clamping fix in #1352, pixel output for some indexed images will differ. No code change needed; re-baseline any image/rendering snapshot tests.
9. Encoding.TryGetNamedEncoding is nullable-annotated (#1389) — source-breaking under NRT
// BEFORE (v0.1.15)publicstaticboolTryGetNamedEncoding(NameTokenname,outEncodingencoding)// AFTER (v0.1.16)publicstaticboolTryGetNamedEncoding(NameToken?name,outEncoding?encoding)
Only affects projects with nullable reference types enabled — assign to an Encoding? local, or use the out var form.
10. Imported-page transforms are now concatenated (#1387) — behavioural
PdfContentTransformationReader.GetGlobalTransform used to keep only the last top-level cm operation, and PdfPageBuilder only inverted the transform of the last content stream that had one. Both now multiply the matrices together. Pages copied with PdfDocumentBuilder.AddPage(document, pageNumber) from documents that emit several top-level cm operations are now positioned correctly (fixes #1163); output for those pages changes.
11. Lenient parsing is more forgiving — two new "don't throw" paths — behavioural
Entries in a page /Contents array that are not an IndirectReferenceToken are skipped instead of throwing PdfDocumentFormatException, when UseLenientParsing is on (#1388, fixes #1286).
A form XObject with an unusable /BBox is left unclipped instead of throwing, when UseLenientParsing is on (#1373, fixes #1371). With strict parsing it still throws.
If you were catching PdfDocumentFormatException to detect these documents, they now open successfully instead.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Release v0.1.16
Full list of changes: https://github.com/UglyToad/PdfPig/releases/tag/v0.1.16
Disclaimer: the below was generated with assistance of AI
✨ New Features
Annotation.NormalAppearance/RollOverAppearance/DownAppearance/AppearanceState, plusAppearanceStream.StatelessAppearanceStream.FontDetails.FontDictionaryReferencenullfor inline/synthetic fonts.Structure.FilterProviderILookupFilterProvideris now public, and a custom provider is correctly propagated (fixes #1243).XmpMetadata.GetXmlMemory()ReadOnlyMemory<byte>so you can feed your own hardenedXmlReader.GetXDocument()now carries a warning: it parses without security settings.ParsingOptionsunsealedParsingOptions.IResourceStore.TryGetXObjectReferenceIndirectReferencewithout reading its stream — lets a processor recognise an XObject it has already seen.ArrayToken.TryToRectangle()/TryToIntRectangle()PdfRectangleconversion.ToRectangle()now also tolerates arrays with more than 4 entries.PdfDocEncodingEncodingimplementation; accepted as a font/Encodingvalue when lenient parsing is on (fixes #1284).BaseStreamProcessor.GetFormOperations()protectedhelper — parses a form XObject's content stream with caching. Custom processors get form caching for free.ITokennow properly overridesEquals(object)/GetHashCode();ArrayTokenandDictionaryTokenpre-compute their hash. Tokens are now usable as dictionary/set keys. See breaking change #5.ColorSpaceDetails.csis nowGraphics/Colors/ColorSpaces/*.cs. Namespaces and public names are unchanged.Type4Programover a stack-allocatedOperandStack: allocation-free and boxing-free evaluation. All the types involved areinternal, so no public API change.DeviceNtint caching, colour-space caching inResourceStore, span-basedGetColor,StackDictionary<>for marked content / shadings / patterns, and caching of scanned tokens, resolved resources and form XObjects.netstandard2.1packagenetstandard2.1;Plus a batch of parsing / rendering bug fixes (no API change): infinite recursion in
GlyphDataTable.ReadCompositeGlyph#1348, out-of-range character codes in Type 3 fonts #1350, value clamping inIndexedColorSpaceDetails#1352, abbreviated key precedence in inline images #1353, infinite recursion resolving default substitute colour spaces #1355, missing/ColorSpacein JPX images #1357, page contents stored as an indirect array inPdfPageBuilder#1358, lost stream position in the brute-force xref scan #1360, unencrypted-metadata handling inDecryptInternal()#1395, theNameToken.GetHashCode()race condition #1392, and stack-depth tracking inCMapParser#1396.PdfMergeralso no longer leaks thePdfDocuments it opens internally, and opens files withFileShare.ReadWrite | FileShare.Delete(#1382, fixes #1234). Streams and byte arrays you pass in are still owned by you and are not closed.1.
ColorSpaceDetails.GetColornow takes aReadOnlySpan<double>(#1378)The abstract
GetColor(params double[] values)was replaced byGetColor(ReadOnlySpan<double> values). There is noparamsoverload left, soparams-style call sites andIReadOnlyList<double>call sites both break.GetRgb(ReadOnlySpan<double>, out double, out double, out double)is unchanged. If you only need RGB, prefer it — it avoids allocating anIColor.2.
IColorSpaceContextcolour setters takedouble[](#1380)SetStrokingColor/SetNonStrokingColorchanged fromIReadOnlyList<double>todouble[], so the operands can be stored in the graphics state without a copy.3. Colour operations:
Operandsis now aReadOnlySpan<double>, and the types aresealed(#1380)Affects
SetStrokeColor(SC),SetNonStrokeColor(sc),SetStrokeColorAdvanced(SCN) andSetNonStrokeColorAdvanced(scn). The...Advancedconstructors also changed fromIReadOnlyList<double>todouble[].Deriving from these four operation classes is no longer possible; implement
IGraphicsStateOperationdirectly instead.4.
IResourceStore— newTryGetXObjectReferencemember (#1391)Adding a member to a public interface breaks custom implementers.
5. Token types are
sealedand now use value equality (#1362)CommentToken,DictionaryToken,EndOfLineToken,IndirectReferenceToken,NameTokenandNullTokenare nowsealed— subclassing them no longer compiles.Behavioural: every token now overrides
Equals(object)andGetHashCode(). Code that relied on reference equality —HashSet<IToken>,Dictionary<IToken, T>,list.Contains(token),Distinct()— will now treat structurally equal tokens as the same entry.6.
PdfPageBuilder.ResetColor()no longer restores the whole graphics state (#1385)SetStrokeColor()/SetTextAndFillColor()used to emit aq(push) andResetColor()a matchingQ(pop), soResetColor()silently reverted the CTM, clipping path, line width and everything else changed in between — and unbalanced calls corrupted the stack. NowResetColor()only resets the stroking and non-stroking colours to black (ISO 32000-1 table 52) and leaves the rest of the state alone.If you were relying on the old save/restore behaviour, wrap the section explicitly:
Related, same PR:
DrawRectangle,DrawTriangleandDrawEllipsisused to re-emitSetLineWidth(lineWidth)after drawing instead of restoring the default; they now correctly restoreSetLineWidth(1). Shapes drawn after one of these calls without an explicit width will now use width 1.7.
BaseNumberOfColorComponentsrecurses forDeviceN/Separation, andSeparation.BaseTypechanged (#1376) — behaviouralDeviceNColorSpaceDetails.BaseNumberOfColorComponentsandSeparationColorSpaceDetails.BaseNumberOfColorComponentsnow returnAlternateColorSpace.BaseNumberOfColorComponentsinstead ofAlternateColorSpace.NumberOfColorComponents. For a nested alternate space (e.g.SeparationoverDeviceNoverDeviceCMYK, or over anICCBased), the previous value was wrong.SeparationColorSpaceDetails.BaseTypeis now the alternate space'sTyperather thanColorSpace.Separation.8. Indexed colour tables decode through the base colour space (#1369, #1375) — behavioural
Indexed colour-table bytes are now decoded into the base space's native component ranges (ISO 32000-2, 8.6.6.3:
min + byte / 255 × (max − min)) instead of being treated as[0, 1]for every base space. Colours fromIndexedspaces overLab— and any other space whose components are not in[0, 1]— change; they were previously wrong. Combined with the clamping fix in #1352, pixel output for some indexed images will differ. No code change needed; re-baseline any image/rendering snapshot tests.9.
Encoding.TryGetNamedEncodingis nullable-annotated (#1389) — source-breaking under NRTOnly affects projects with nullable reference types enabled — assign to an
Encoding?local, or use theout varform.10. Imported-page transforms are now concatenated (#1387) — behavioural
PdfContentTransformationReader.GetGlobalTransformused to keep only the last top-levelcmoperation, andPdfPageBuilderonly inverted the transform of the last content stream that had one. Both now multiply the matrices together. Pages copied withPdfDocumentBuilder.AddPage(document, pageNumber)from documents that emit several top-levelcmoperations are now positioned correctly (fixes #1163); output for those pages changes.11. Lenient parsing is more forgiving — two new "don't throw" paths — behavioural
/Contentsarray that are not anIndirectReferenceTokenare skipped instead of throwingPdfDocumentFormatException, whenUseLenientParsingis on (#1388, fixes #1286)./BBoxis left unclipped instead of throwing, whenUseLenientParsingis on (#1373, fixes #1371). With strict parsing it still throws.If you were catching
PdfDocumentFormatExceptionto detect these documents, they now open successfully instead.New contributors: @MultisoftPontus (#1358). Thanks also to @jeske for a large share of this release (#1359, #1360, #1367, #1369, #1375).
Full changelog: v0.1.15...v0.1.16
All reactions