From 4ab2e7fdb81b0af33f4d42551331e9b4c3fe60dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Feb 2026 15:35:58 +0000 Subject: [PATCH 1/9] Initial plan From f35e95eb82aaf594c4ebc380685b70a8baf248d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Feb 2026 15:43:39 +0000 Subject: [PATCH 2/9] Fix PDF Kids array to use indirect page references per PDF spec Co-authored-by: Shane32 <6377684+Shane32@users.noreply.github.com> --- QRCoder/PdfByteQRCode.cs | 46 +++++++++++------- ...der_pdfbyte_qrcode_blackwhite.approved.pdf | Bin 2478 -> 2518 bytes ...n_render_pdfbyte_qrcode_color.approved.pdf | Bin 2478 -> 2518 bytes ...der_pdfbyte_qrcode_custom_dpi.approved.pdf | Bin 2478 -> 2518 bytes ...er_pdfbyte_qrcode_from_helper.approved.pdf | Bin 2481 -> 2521 bytes ..._pdfbyte_qrcode_from_helper_2.approved.pdf | Bin 2478 -> 2518 bytes ...erificationTests.pdf_renderer.approved.pdf | Bin 1945 -> 1985 bytes 7 files changed, 28 insertions(+), 18 deletions(-) diff --git a/QRCoder/PdfByteQRCode.cs b/QRCoder/PdfByteQRCode.cs index dd80fc84..1a020e31 100644 --- a/QRCoder/PdfByteQRCode.cs +++ b/QRCoder/PdfByteQRCode.cs @@ -92,18 +92,12 @@ public byte[] GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string li // Object 2: Pages - defines page tree structure writer.Write( - ToStr(xrefs.Count) + " 0 obj\r\n" + // Object number and generation number (0) - "<<\r\n" + // Begin dictionary - "/Count 1\r\n" + // Number of pages in document - "/Kids [ <<\r\n" + // Array of page objects - begin inline page dictionary - "/Type /Page\r\n" + // Declares this as a page - "/Parent 2 0 R\r\n" + // References parent Pages object - "/MediaBox [0 0 " + pdfMediaSize + " " + pdfMediaSize + "]\r\n" + // Page dimensions [x1 y1 x2 y2] - "/Resources << /ProcSet [ /PDF ] >>\r\n" + // Required resources: PDF operations only (no images) - "/Contents 3 0 R\r\n" + // References content stream (object 3) - ">> ]\r\n" + // End inline page dictionary and Kids array - ">>\r\n" + // End dictionary - "endobj\r\n" // End object + ToStr(xrefs.Count) + " 0 obj\r\n" + // Object number and generation number (0) + "<<\r\n" + // Begin dictionary + "/Count 1\r\n" + // Number of pages in document + "/Kids [ 3 0 R ]\r\n" + // Kids must contain indirect references to Page objects + ">>\r\n" + // End dictionary + "endobj\r\n" // End object ); // Content stream - PDF drawing instructions @@ -122,13 +116,29 @@ public byte[] GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string li writer.Flush(); xrefs.Add(stream.Position); - // Object 3: Content stream - contains the drawing instructions + // Object 3: Page - indirect page object (Kids array must reference pages indirectly) + writer.Write( + ToStr(xrefs.Count) + " 0 obj\r\n" + // Object number and generation number (0) + "<<\r\n" + // Begin dictionary + "/Type /Page\r\n" + // Declares this as a page + "/Parent 2 0 R\r\n" + // References parent Pages object + "/MediaBox [0 0 " + pdfMediaSize + " " + pdfMediaSize + "]\r\n" + // Page dimensions [x1 y1 x2 y2] + "/Resources << /ProcSet [ /PDF ] >>\r\n" + // Required resources: PDF operations only (no images) + "/Contents 4 0 R\r\n" + // References content stream (object 4) + ">>\r\n" + // End dictionary + "endobj\r\n" // End object + ); + + writer.Flush(); + xrefs.Add(stream.Position); + + // Object 4: Content stream - contains the drawing instructions writer.Write( - ToStr(xrefs.Count) + " 0 obj\r\n" + // Object number and generation number (0) - "<< /Length " + ToStr(content.Length) + " >>\r\n" + // Dictionary with stream length in bytes - "stream\r\n" + // Begin stream data - content + "endstream\r\n" + // Stream content followed by end stream marker - "endobj\r\n" // End object + ToStr(xrefs.Count) + " 0 obj\r\n" + // Object number and generation number (0) + "<< /Length " + ToStr(System.Text.Encoding.ASCII.GetByteCount(content)) + " >>\r\n" + // Dictionary with stream length in bytes + "stream\r\n" + // Begin stream data + content + "endstream\r\n" + // Stream content followed by end stream marker + "endobj\r\n" // End object ); writer.Flush(); diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_blackwhite.approved.pdf b/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_blackwhite.approved.pdf index fde095c8847342aa957b7e99e21625459c26814e..fbbca52daa5a6904e88a51907c9ac0e32fe74d23 100644 GIT binary patch delta 142 zcmZ1{d`);ljFhp0fkKc%ESH@fS885Leo__}NCZSrj7bqR0m&lCnQXik!og@dS({VV x%1prk1QhbPz%-CBH$)dQGy<9i0ti(`re>4#Ii-MxbaBdXn3{5_s=E5S0RZ{+Aq)Tj delta 134 zcmca6yiRyR%*3)34r2uag&;0FyNQY78^3sSFq%wu;FL`>S14#Ii-MxbaBdXn3{5_s=E5S0RZ{+Aq)Tj delta 134 zcmca6yiRyR%*3)34r2uag&;0FyNQY78^3sSFq%wu;FL`>S14#Ii-MxbaBdXn3{5_s=E5S0RZ{+Aq)Tj delta 134 zcmca6yiRyR%*3)34r2uag&;0FyNQY78^3sSFq%wu;FL`>S1TuKsQS5I`p= delta 137 zcmca9yis^U%*3)Z4r2uag&;0FyNQY78^8H*xSDVoz=499sj0E4LK4#Ii-MxbaBdXn3{5_s=E5S0RZ{+Aq)Tj delta 134 zcmca6yiRyR%*3)34r2uag&;0FyNQY78^3sSFq%wu;FL`>S1 Date: Sat, 21 Feb 2026 11:02:59 -0500 Subject: [PATCH 3/9] Apply suggestion from @Shane32 --- QRCoder/PdfByteQRCode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QRCoder/PdfByteQRCode.cs b/QRCoder/PdfByteQRCode.cs index 1a020e31..86b7e3c5 100644 --- a/QRCoder/PdfByteQRCode.cs +++ b/QRCoder/PdfByteQRCode.cs @@ -134,7 +134,7 @@ public byte[] GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string li // Object 4: Content stream - contains the drawing instructions writer.Write( - ToStr(xrefs.Count) + " 0 obj\r\n" + // Object number and generation number (0) + ToStr(xrefs.Count) + " 0 obj\r\n" + // Object number and generation number (0) "<< /Length " + ToStr(System.Text.Encoding.ASCII.GetByteCount(content)) + " >>\r\n" + // Dictionary with stream length in bytes "stream\r\n" + // Begin stream data content + "endstream\r\n" + // Stream content followed by end stream marker From 1f3999fdecd3d794d8c8c49703a6371631181be3 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 21 Feb 2026 11:19:25 -0500 Subject: [PATCH 4/9] Fix tests --- ...der_pdfbyte_qrcode_blackwhite.approved.pdf | Bin 2518 -> 2518 bytes ...n_render_pdfbyte_qrcode_color.approved.pdf | Bin 2518 -> 2518 bytes ...der_pdfbyte_qrcode_custom_dpi.approved.pdf | Bin 2518 -> 2518 bytes ...er_pdfbyte_qrcode_from_helper.approved.pdf | Bin 2521 -> 2521 bytes ..._pdfbyte_qrcode_from_helper_2.approved.pdf | Bin 2518 -> 2518 bytes ...erificationTests.pdf_renderer.approved.pdf | Bin 1985 -> 1985 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_blackwhite.approved.pdf b/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_blackwhite.approved.pdf index fbbca52daa5a6904e88a51907c9ac0e32fe74d23..66fa6227c6caee4e0a042eed9238f716a8f9dca5 100644 GIT binary patch delta 78 ycmca6d`);mET@&Zf&mC9 Date: Sat, 21 Feb 2026 16:42:27 +0000 Subject: [PATCH 5/9] plan: fix git line ending corruption of PDF files Co-authored-by: Shane32 <6377684+Shane32@users.noreply.github.com> --- QRCoderTests/PdfByteQRCodeRendererTests.cs | 81 ++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.cs b/QRCoderTests/PdfByteQRCodeRendererTests.cs index d41fc82a..4416da4f 100644 --- a/QRCoderTests/PdfByteQRCodeRendererTests.cs +++ b/QRCoderTests/PdfByteQRCodeRendererTests.cs @@ -1,3 +1,8 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; using QRCoder; using Shouldly; using Xunit; @@ -54,4 +59,80 @@ public void can_render_pdfbyte_qrcode_from_helper_2() var pdfCodeGfx = PdfByteQRCodeHelper.GetQRCode("This is a quick test! 123#?", 5, "#FF0000", "#0000FF", QRCodeGenerator.ECCLevel.L); pdfCodeGfx.ShouldMatchApproved("pdf"); } + + private static readonly char[] _lineEndChars = { '\r', '\n' }; + + [Fact] + public void pdf_xref_table_is_valid() + { + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); + var pdfBytes = new PdfByteQRCode(data).GetGraphic(5); + + // Parse from the end to find startxref + var pdfText = Encoding.ASCII.GetString(pdfBytes); + + // Find %%EOF at the end, then work backward to find startxref + var eofIndex = pdfText.LastIndexOf("%%EOF", StringComparison.Ordinal); + eofIndex.ShouldBeGreaterThan(0, "%%EOF not found"); + + var startxrefIndex = pdfText.LastIndexOf("startxref", eofIndex, StringComparison.Ordinal); + startxrefIndex.ShouldBeGreaterThan(0, "startxref not found"); + + // Read the xref byte offset (the number on the line after "startxref") + var afterStartxref = pdfText.IndexOf('\n', startxrefIndex) + 1; + var endOfOffset = pdfText.IndexOfAny(_lineEndChars, afterStartxref); + var xrefOffsetStr = pdfText.Substring(afterStartxref, endOfOffset - afterStartxref).Trim(); + var xrefOffset = int.Parse(xrefOffsetStr, CultureInfo.InvariantCulture); + xrefOffset.ShouldBeGreaterThan(0, "xref byte offset should be positive"); + + // Seek to xref table and parse it + using var stream = new MemoryStream(pdfBytes); + stream.Position = xrefOffset; + var reader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true); + + // First line must be "xref" + var xrefLine = reader.ReadLine(); + xrefLine.ShouldBe("xref", "xref keyword not found at expected offset"); + + // Parse subsections: "firstObjNum count" + var objectOffsets = new Dictionary(); + string? subsectionLine; + while ((subsectionLine = reader.ReadLine()) != null && subsectionLine != "trailer") + { + var parts = subsectionLine.Trim().Split(' '); + parts.Length.ShouldBe(2, $"Expected 'firstObj count' but got: {subsectionLine}"); + var firstObj = int.Parse(parts[0], CultureInfo.InvariantCulture); + var count = int.Parse(parts[1], CultureInfo.InvariantCulture); + + for (int i = 0; i < count; i++) + { + // Each entry: "NNNNNNNNNN GGGGG f\r\n" or "NNNNNNNNNN GGGGG n\r\n" + var entry = reader.ReadLine(); + entry.ShouldNotBeNull(); + var entryParts = entry!.Trim().Split(' '); + entryParts.Length.ShouldBe(3, $"Expected 'offset gen type' but got: {entry}"); + var offset = long.Parse(entryParts[0], CultureInfo.InvariantCulture); + var type = entryParts[2]; + type.ShouldBeOneOf("n", "f"); + + if (type == "n") + objectOffsets[firstObj + i] = offset; + } + } + + objectOffsets.Count.ShouldBeGreaterThan(0, "No in-use objects found in xref table"); + + // Verify each object: seek to its offset and confirm "N 0 obj" is present + foreach (var kvp in objectOffsets) + { + stream.Position = kvp.Value; + var objNum = kvp.Key; + var offset = kvp.Value; + var objReader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true); + var objLine = objReader.ReadLine(); + objLine.ShouldNotBeNull($"No content at offset {offset} for object {objNum}"); + objLine.ShouldBe($"{objNum} 0 obj", $"Object {objNum} at offset {offset} did not start with '{objNum} 0 obj'"); + } + } } From 210d90e19dfcb59b27b6a047b0bb5b4f49c996db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Feb 2026 16:46:20 +0000 Subject: [PATCH 6/9] Fix git line-ending corruption of PDF files; add xref validation test Co-authored-by: Shane32 <6377684+Shane32@users.noreply.github.com> --- .gitattributes | 4 ++-- QRCoder/PdfByteQRCode.cs | 2 +- ...der_pdfbyte_qrcode_blackwhite.approved.pdf | Bin 2518 -> 2728 bytes ...n_render_pdfbyte_qrcode_color.approved.pdf | Bin 2518 -> 2728 bytes ...der_pdfbyte_qrcode_custom_dpi.approved.pdf | Bin 2518 -> 2728 bytes ...er_pdfbyte_qrcode_from_helper.approved.pdf | Bin 2521 -> 2731 bytes ..._pdfbyte_qrcode_from_helper_2.approved.pdf | Bin 2518 -> 2728 bytes ...erificationTests.pdf_renderer.approved.pdf | Bin 1985 -> 2153 bytes 8 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitattributes b/.gitattributes index 412eeda7..8b42d130 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,7 +16,7 @@ *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain -*.pdf diff=astextplain -*.PDF diff=astextplain +*.pdf binary +*.PDF binary *.rtf diff=astextplain *.RTF diff=astextplain diff --git a/QRCoder/PdfByteQRCode.cs b/QRCoder/PdfByteQRCode.cs index 86b7e3c5..ae994813 100644 --- a/QRCoder/PdfByteQRCode.cs +++ b/QRCoder/PdfByteQRCode.cs @@ -72,7 +72,7 @@ public byte[] GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string li // Binary comment - ensures PDF is treated as binary file (prevents text mode corruption) stream.Write(_pdfBinaryComment, 0, _pdfBinaryComment.Length); - writer.WriteLine(); + writer.Write("\r\n"); writer.Flush(); xrefs.Add(stream.Position); diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_blackwhite.approved.pdf b/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_blackwhite.approved.pdf index 66fa6227c6caee4e0a042eed9238f716a8f9dca5..ca00f1f97f7e77011ba1947099d7aceab06d3ba8 100644 GIT binary patch literal 2728 zcmZ9O!EVz)5QguT{0@8R1*x^Ywv$GRdZ5q?AWF-v>LIjlDJn5Iu2gtQ4!i-p2;Z#N zI}_5V^6!7<|7Ui0VjrBpIy^Y_>9JgZ#k`cGf-Y`xC2;^U8Rohwc^TL{q!k{rK5=-_#*q)~31y}a5rYs}rf0K^9^YAn<}^|abZP<9<1VZTB06FsqDWSVCT2-W)W_G| zi#+?li`x7)GrzMv4_$am;pMMPAGc`Rx^+Ut4@Tk2ZS~%gH#7N|dli-cf6uojcN{hk nkKGX8EH58^b`Iy_qVGq_PA_J=aWjllX})b2yqF%met-54Tn-o& literal 2518 zcmZ9O&2HO96ovQm6tnRrKul^xN-bSg&&Gj_iy%}aQHFObgBV{#L<8g-T6gy>K z<(6J@$L81J_%MHnDRo==W?P71??MmJviwOFZ+@7Af+{Imm?hv;ndegd@!rZ<` zPR3}^B$@>ayV*;HxquzTI=c$5ZM$7D>ZH_bF|b<<>}K!cvr4l=bapdSyURWDQv2q@ zWhP@WWHKHynZ1h_H4~$8S@t@Pl^sfpfyQLcg1xlHQR)=3rz(ukJ=9}(JS zUS=6SV^4oo2_9m;^J};-ZvS;a7a0^rVz7kWhZ8L&x zKWN){mE{WA4Uy>8AS~C)lgXA*kkA$!Vr@t{BBo*vmTILCv#&J*C$K<(m)ort$zdz6 z7A;CGx&oIe)Cfi40&eS#(d=5T3!5WQ^Qd2gD2aR4VT^>sD@iD@%@<7t(^6Zzl2m@o z@T4_F`?#rLIx10>fk9x32wY)RT!GC9aPXWcctL#gN#jZmtLs7zwIODNqxTyXJ@Z|fBh5iYYl4v diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_color.approved.pdf b/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_color.approved.pdf index 725e7e5e76f73a417978844174be562dd06b7df7..12bb9212dc9286b1642b1bbbf7af4a8be0322aa3 100644 GIT binary patch literal 2728 zcmZ9O&u-H|5XSG8{0@8R1*x^Ywv$GRdZ5q?AWF-v>LIjlDJn5Iu2gtQ4!i-p2)|jc zcP2zp#CH-B~}_QCn9v%~7B&9j3)e}DbXv&xmO|MVr#*6TbgKK}UDx#Dy)ZXWx4 z@cHJx+c}TTMV_6UxSXXsFf zfr@wC?Zf6}|Lm?xe3q*tAAZm)F1lU+G~AHYdhLqyp})E8Mj{pz=B}OOSe*9T5xJdf zB#r$fq!k{rK5=-_*f#HxAwAYo2}2Gk+wOr8}H1LJl`ja<6g&&5^6RmhV6b zcT+3NKs7{OiWx-Z0o4qINnl}sFD($CS)g&Px|9~w0Fje0-W$dr8_>8#@?UUWM~|vB z1X&tp3Ggbc{$9jp0iq}~|$OhD)LN=`T?fV4&`7q?N*p3l5A3JA2^W_j!bl)I3@*>dmBbNCTOFj_81&K)7L?gsk08*)xcF@qLiwoD15 zfo<#u8q>c;D4JNhE>g4_O3?=l9%XhO1;Lhbe+_=ulB4Dh4rD2BE1cP&}uih`5=pA)G?Y+*52DaZ z^wknKPD}OMJ$_k$x;Nx#R*D>zl4u4YXAsd8No@x0 z`AKYo#MqvrQcEbaeTW!@$TB^fW%c;ZA{3{V5o#)ZQVK{;)g)t%5C-Dk~cH?n0pnO|9{W7#&;Yx o50Bjt-z+a5es&VMxaj+lw9|{(Zrlvxl$vkb1uv!tuiu~j15%C{6aWAK literal 2518 zcmZ8j%Wl&^6y48P%%&Tp*7n#=8Y$|6LN|aYExW3V(7GY0jlppx;3rw|2k;}DGvhhe zB8sB>IQQJg+=+d3`Rd}hI%(a}pTEC;yULfo|MbPJ*RJ^Z<7?-O^Ub(boaLQ{{(k6zbaJJ9O?LPOGH7LAE6!ovQ-m zYLaQs^1|d$&Sc`J*v7YlmovGBOd`;SVD@loa^n{{bOEtW=&Cd`3N!l>auB0J18JsM zu*;q*j0G%Etg*B3+_u??VJE3xVZbg7*ky10X{K2~GfF|S&&VRDxz|JR7p@O1}UWk?0{k* zryXVefTXLz4MAZ|P8p(y4DcYo?gM5onbo#&Ims10Q^yP!jOp7*2c3e zQ;=N|0qrRS%Qf>vvZ;F{)PjSU8=|Smfu%B30A`tMM7TB;;puj>MdZ-RvqhoUqAB2# zf*L`Q(j#2!4WsN_u9fD9PPqO3r0(2?ut_SGlM70L%5Gs zhViIKl?Da^DI_ojt6~b+L;we#6Dgh{w)se7N`&QgK^ol&IJkEyKjKZn4r`5h5V{oD z1oPkz!eZdS2_!NU+2|U9SPrheDG@e>u>SNg;LJcQ7(#FyB5voza&WMw#1MAFlL41J zoSiNRQb3^(Fv!|{)Mb|fb;-djrhv#4WRb#NOqs_pCD>C++ysNcVV4K-rXYTqpM_jA ze5ZIcBN&-=;UK4n4f{vD?g-Q4!0j~qCEA{b< zwyj$a^Yj6Kl>A<%C0=p(F;j(F|1*Ww0>@#qd+dhzLUz6T*&$!juXY2KRmVNl~wF%kTrr$ia z58#W^mIOJc6S;ruPp7XecJV*AmR1)ZXd2gDH-ABFh^cm&R``5>eDqS zj7Hhp=5D)r(S8rtIXUI(c%gsL%PyOJ`_$c1)p{MWi>|%BYI+sTButqf%u zI$F3NUgSzuI4>m(qKE*^5g<$g3kPEEf#ggARj4f`_n-uboY=(Zn0Vqq6&5Lf!E-rg z6uBd)(lJXwG-1n+A~p%&V$4wQP|JearyyLUk+c?$kuYg(EZ@R#u`pbWslrJTTkz~$ zjGz|>YQWS>v2uVFvoI`84NGIHuu7IRdOn_1)l;*W=l^KMOMi>Dkn0+QHjkH+hjm$uk}c)qH@zB zh=xTcR2<@1IfSOEKt=toQajSz)L`e3 zMy8{MolO*!oYb)rL>%Jm+wqruvXSD*IF3Pdc~I=nC`rYY4I=tR$;ddQkyDzi!tg_+ z&7p6Rq_JD3UmxfjC2Q`Grb&0Yb0``oi)!6##7BPJq-WQAyAlB7iY z`MQ5LpS|azHr-~XJKOiJ(OU|azdU`yqOQw22r+&z(xjf9nqgU_F{{bTt7!Lpd literal 2518 zcmZ9OO>W#q5QXCe%&7lE1m&_tJa4wSX zb+fC8jD$h`yn6N1)kB_LKY#h8Js+yGpa1^-Pt}GdOn-c=E-$P4_a9%zP+uLE!>8$? zs;>_Z<0(V{H`TLe)p)!YP-0UmZ@_mK=ezWETh%w?Y5F$bQOD(FsITYg?%lYMsiuo?8z`&3nvM%xr_d|= zCb#sQJJheoopAQhJoGeeRx`Ec8JbyW@>kNKwfKa zFI;9ah9Q&jkjdKq z27$u!Od&7@N8T*uBwpo~$#0YdE7KvT!IU#vlL_Vr?Jyi&L2*Fr`kIAo=3uW;N`9(z zg_Nd5cEDng^Ny;1Ao*%&Lny2-DP!`80T21@J}`TeRqc?LQ(VcjcC2uNDg980so1s| z!M2}h+jy1b3fV0p(yN8ATpLd!TSq}cTX2YN!CR(c4wh=8fZ3NC2?v;aCp_P7wMY(I zd9`RLwde|5rcfgknTv2+Z;WQwa$VRQ2{n)UwGbt7&lbi=IJ}&M0-JQvR4^?yr7KDK z#|%&0hG-w#0@G26D(^G|rij25R>c+Ai~vV0mr^0V`J{0r!OFUj#_j|T?Oo1KyesUm zt+5WmE(M$59)2J^1_vjQ$tbe7YXq@5wDztf*czhx^TR+hgVCaG!Mc({ z)QwICF7s%1c0oviA|Ehh+kNWtOF>=cP!?Apa)m6Ws25kZF-i%0P8m51gW>SYL%b`* z@5{4LYsUW?PcsG+vt2mk{IF5~#M>QFyeS$6Wv^tEWe<=Kzf^B|+gV={*7u(8^Qh;Ft~@W5q0JboJI^g{OT@vo7L_02Rb)VaRi UoR-79q;ldg?5eY~7r(vy54*YzWdHyG diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_from_helper.approved.pdf b/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_from_helper.approved.pdf index e80e9033e89a38095af58ce54638b31ba1e755b6..ea8d941a1a447d773a76be5a9c98056bfd75d5e4 100644 GIT binary patch literal 2731 zcmZ9O&u-H|5XSG8{0@8R1*x^YvGYfYdZ5q?AWF-v>LIjl2`Vu-u2gtQ4!i-p2*262 zcP4}i`SUmPeKWf|$sS$2IzO&XnmjxD^Y_>9JgZ#kx=&y7Y`xC2;^U8Rtt-wp!{)Jj z0AFk#+P(9{T;|#7X`Z#ayAUm}H0`W=+6}HE;m!7L@2=fUGThB!j=a>IXe9>0r*Bah zjf!{e-FEY`dv@0)IaRYfspJ1B7MJb5d+KkgYrS^GMc>_CwF907t+^W~RTgL6Za{GF zYANKf40$z{;flBI?qT@syszt!xgYv=^EJ=D=UIKSB(ikJ@gY`m3s*V$*RwuC&t~%1 z)5U#!Q3k5UdLdyDl?Q|z0m39OH^7$`NX{fsyGBz=3uXY36C3Xhqu%q_r@Ngh^{-`5K0chT)=2?UqSw&a-h* zf?f=008=m3$^ceO!?08}ES0Ibuo& zh=xW=Ax+Y!ew_oJbas%?agjz0q7KdSw6AF#M#}}sX?<|k*+5@sL-t82&Y(qvEmFZ~ z5Ie318u8mAG>w;zi!`l)(zHP+DxzR?MnyDqFnFzu9LANdF!{>K5@?#38SWsGg<^k4 zGpa4aw1)EOu}p2sWBk4p&L5=78Lm>KY^Df~!+?q+Q89>PWe}RG0_DSZRj?z?ObvDh zg~)WYu+xcxl9M`Cf`~z!eLeoNPdZW@8N)G%CJ&1J86~N>)Nf}aO39iV6w;)-uyZIXCyT1{g%wCxi3I4}P@@?sYE(+x3_{Kz+!RS~ z1|8&yHzuREXR9<4%1j?31|hOYzs)jw{7oV>r;%cyQwxYccVSHs(Ge37MY2LPF-cOQ z{(RlP$g>aJ)u!vrbZvX?+i*+a{#T}tn>Wp@aYBqAjKY)K>AMA&Gx?Zz6_x-0&NnN4 qH}sqBW806H%d73r*5O=ScHKbP>1MVcHvJGw^G&njZhG|k{rNux4H&lo literal 2521 zcmZ8j%Wl&^6y48P%qAP8*7n5CLyEef&N00sIK(%y`bV zNTcdL&OP@rcVf>jUtOG3r;VHa`TOg)t9;(UL)O-kk(ZJ za)d*sJzoOrF`4!}j)@(V!hl9H&Z3Ovr}KuArw~0^!T8LB8l>R-Ckhf98wePlW(oo+ zaHP#*PQ4d;?E07m+2p`pL&^D3r71`$C13{> z13B#|>jxxV4Q>but8>Z_J!F6f`E?&Kd&#V}iOWf@=ovd^xL{1*WMWjTZHA!jN3=Gc zWtoEPiU{acAy}@7Cz6e$Kte4zh_&DqrXmNHYN7zM%rzn$z|=d!)9q%9$f1>Ii$bwQ zQ@|w!HG(2>AzbSXqwHL+wdROWdDyQ)n8Z9&7)FA_OG!{*87~?OMoVSxic0!W`fmkqv;5bCwOo`>-U`>f3?1m=; zE_paRT@a*zLLXp|wfm^cE(Pk6gIP=gktxU`g}sI#aZ(OO5 zUo_3U@i31c@JGoXWLn}ahaWRlsP#WnXx1_f{dWJ@_VJzUdjGRU#^S2$2Glve-yDZ+ QKg4oE)2!WW_WJ$BKOu$=4FCWD diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_from_helper_2.approved.pdf b/QRCoderTests/PdfByteQRCodeRendererTests.can_render_pdfbyte_qrcode_from_helper_2.approved.pdf index 725e7e5e76f73a417978844174be562dd06b7df7..12bb9212dc9286b1642b1bbbf7af4a8be0322aa3 100644 GIT binary patch literal 2728 zcmZ9O&u-H|5XSG8{0@8R1*x^Ywv$GRdZ5q?AWF-v>LIjlDJn5Iu2gtQ4!i-p2)|jc zcP2zp#CH-B~}_QCn9v%~7B&9j3)e}DbXv&xmO|MVr#*6TbgKK}UDx#Dy)ZXWx4 z@cHJx+c}TTMV_6UxSXXsFf zfr@wC?Zf6}|Lm?xe3q*tAAZm)F1lU+G~AHYdhLqyp})E8Mj{pz=B}OOSe*9T5xJdf zB#r$fq!k{rK5=-_*f#HxAwAYo2}2Gk+wOr8}H1LJl`ja<6g&&5^6RmhV6b zcT+3NKs7{OiWx-Z0o4qINnl}sFD($CS)g&Px|9~w0Fje0-W$dr8_>8#@?UUWM~|vB z1X&tp3Ggbc{$9jp0iq}~|$OhD)LN=`T?fV4&`7q?N*p3l5A3JA2^W_j!bl)I3@*>dmBbNCTOFj_81&K)7L?gsk08*)xcF@qLiwoD15 zfo<#u8q>c;D4JNhE>g4_O3?=l9%XhO1;Lhbe+_=ulB4Dh4rD2BE1cP&}uih`5=pA)G?Y+*52DaZ z^wknKPD}OMJ$_k$x;Nx#R*D>zl4u4YXAsd8No@x0 z`AKYo#MqvrQcEbaeTW!@$TB^fW%c;ZA{3{V5o#)ZQVK{;)g)t%5C-Dk~cH?n0pnO|9{W7#&;Yx o50Bjt-z+a5es&VMxaj+lw9|{(Zrlvxl$vkb1uv!tuiu~j15%C{6aWAK literal 2518 zcmZ8j%Wl&^6y48P%%&Tp*7n#=8Y$|6LN|aYExW3V(7GY0jlppx;3rw|2k;}DGvhhe zB8sB>IQQJg+=+d3`Rd}hI%(a}pTEC;yULfo|MbPJ*RJ^Z<7?-O^Ub(boaLQ{{(k6zbaJJ9O?LPOGH7LAE6!ovQ-m zYLaQs^1|d$&Sc`J*v7YlmovGBOd`;SVD@loa^n{{bOEtW=&Cd`3N!l>auB0J18JsM zu*;q*j0G%Etg*B3+_u??VJE3xVZbg7*ky10X{K2~GfF|S&&VRDxz|JR7p@O1}UWk?0{k* zryXVefTXLz4MAZ|P8p(y4DcYo?gM5onbo#&Ims10Q^yP!jOp7*2c3e zQ;=N|0qrRS%Qf>vvZ;F{)PjSU8=|Smfu%B30A`tMM7TB;;puj>MdZ-RvqhoUqAB2# zf*L`Q(j#2!4WsN_u9fD9PPqO3r0(2?ut_SGlM70L%5Gs zhViIKl?Da^DI_ojt6~b+L;we#6Dgh{w)se7N`&QgK^ol&IJkEyKjKZn4r`5h5V{oD z1oPkz!eZdS2_!NU+2|U9SPrheDG@e>u>SNg;LJcQ7(#FyB5voza&WMw#1MAFlL41J zoSiNRQb3^(Fv!|{)Mb|fb;-djrhv#4WRb#NOqs_pCD>C++ysNcVV4K-rXYTqpM_jA ze5ZIcBN&-=;UK4n4f{vD?g-Q4!0j~qCEA{b< zwyj$a^Yj6Kl>A<%C0=p(F;j(F|1*Ww0>@#qd+dhzLUz6T*&$Vk}M{FtD1A)B&B4oL(_Fxik2wGxrywbu?a^Mf(N2s2$ zr`se^@5xiZTljuwCZ{`wt9hEnQ2eTk#>I*yW$Kfbk;Y?`5Y?2f?O z=Gb1yB4-yz*Vl2>o)0dYm!xddJ)H+KD0p)^Ti6)qUQ@E|yZvoDKr_MC=$_bRvgytP2rrbgk+}_C z8f{SWwmlz*&t%H7bd{H(Z=0`i^gWL9WrfU?uKa^{u}91hzkBqD!?xy6k6k?Yh17*| zNUt~z0h0l^8U_Ubw&Np}O3SH`tW*OF5K0OisT8o9Ag3~rm3Yo3#Z(211qj5P7F<>e zSgx4}pdx90kA5#D_B$n+)NMX&pF1&vLPshEpdtiNk(!*UP+#I%sYn2OQGnBeWTgO9 zga9g%mQyVysRmYq0wA=kQW1~|0nXdEz@0zxF~)d1Et*IVg?+Nh$?)>D)ac~Casm$_#j zl8LdG$p>R0Rmhu-zDl(4AY~Q?B@^5-v2ZUF%O1voT5VDXZC5D03h#s2Yw0h02*lm_$9^x6M7CtoWC&29J6Byv15g> zH$gC{(Toy+Es9BnU{d`6aPoqfQZm7=(To#7-2zDDX&U8RZ0ZnG%5UZ2MI3#=O*>si z)1~{|x9*O??VW}fl|_*i#DVdJ>2ptXmbDeGR(`291I7Q>Y*EYF&^M>Ywjci*+@5~6 e1m$GcbptjL?$yiC^nI#1X7K_*KZ~g(3nygI# literal 1985 zcmZ8iO>fgc5WVlO*h?-*jlJuS*izI3fnER+Qf^faCfSCdHU`_33V)IV{{a37Z+5*i z3vN`+$D8+Nb|>*-`|9S(EGxD6{paT|Wpu9lPhZq}t+J2bzdD_5x~Y5UPb%AXCpT&f zz)oFXD|a~(ko;J%>7Oo>HY$5_K92fM7lhUO1Wl2M4)nxI+=D}DAkW^p$dabkV&>wEy1g#8n=)Fc`*`~itU>kKw`iVOEL0xBW-Q_fW z)~2p&DjKK3b&u+sDwhpVxxNZU5I;QPVCZi+DN5RwK7&*D-pwgqLT*J6r%VCc!(msj z(Y}v2f+=;ykUX(1>qp>Jp z7`W)fd_B~|%F*%xR74Sjaz>#7pHKmHwyAS*P+mqs17hA@QfC;Jm-iQ36;}Y1F0i01 zl)1#BDTaZRw-5dD_EBGTJUR+XCWT=EkfF}z=~p~G^yunB23IO;2z}0=v`MeH;d(%z zB=zjjFzle7*M)i(2P!QOfN&d+iRS0k!wI7*sNk!bM-aFqL3he1Iy-UDFW2Cb!~rqU z&MN{Ns{W8b%n8~k!xMv;GZZ%QV8Ml}WiH-HYLXMwq&S#`9W<3VDH&{PiyIRMjmaY8 zQrRIghJiM5Qa0G~lMEECNRaWF`7nr#7ykZ*`hXYP{5F_hanFOJ-}y@A{OG!>ib^9q zKiDR3=?jNec$?89Ap@cRLbhr-G!5PP!43Z1dwc%jpqTCYenQjpdu*J#Ve;*mwlQk4 Ic>Vt7FX<R From 32e63ad47614da3d21d2995fda62c93d2e398d38 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 21 Feb 2026 11:51:24 -0500 Subject: [PATCH 7/9] Remove usings --- QRCoderTests/PdfByteQRCodeRendererTests.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.cs b/QRCoderTests/PdfByteQRCodeRendererTests.cs index 4416da4f..11c005c5 100644 --- a/QRCoderTests/PdfByteQRCodeRendererTests.cs +++ b/QRCoderTests/PdfByteQRCodeRendererTests.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Globalization; using System.IO; -using System.Text; -using QRCoder; -using Shouldly; -using Xunit; namespace QRCoderTests; From f1d953a952920d9ff98ddf48ffc4bd9df80547ab Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 21 Feb 2026 12:06:33 -0500 Subject: [PATCH 8/9] Update new test --- QRCoderTests/PdfByteQRCodeRendererTests.cs | 35 +++++++++++++++------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.cs b/QRCoderTests/PdfByteQRCodeRendererTests.cs index 11c005c5..86ea5d47 100644 --- a/QRCoderTests/PdfByteQRCodeRendererTests.cs +++ b/QRCoderTests/PdfByteQRCodeRendererTests.cs @@ -69,14 +69,14 @@ public void pdf_xref_table_is_valid() var eofIndex = pdfText.LastIndexOf("%%EOF", StringComparison.Ordinal); eofIndex.ShouldBeGreaterThan(0, "%%EOF not found"); - var startxrefIndex = pdfText.LastIndexOf("startxref", eofIndex, StringComparison.Ordinal); + var startxrefIndex = pdfText.LastIndexOf("startxref\r\n", eofIndex, StringComparison.Ordinal); startxrefIndex.ShouldBeGreaterThan(0, "startxref not found"); // Read the xref byte offset (the number on the line after "startxref") - var afterStartxref = pdfText.IndexOf('\n', startxrefIndex) + 1; - var endOfOffset = pdfText.IndexOfAny(_lineEndChars, afterStartxref); - var xrefOffsetStr = pdfText.Substring(afterStartxref, endOfOffset - afterStartxref).Trim(); - var xrefOffset = int.Parse(xrefOffsetStr, CultureInfo.InvariantCulture); + var afterStartxref = startxrefIndex + "startxref\r\n".Length; + var endOfOffset = pdfText.IndexOf("\r\n", afterStartxref, StringComparison.Ordinal); + var xrefOffsetStr = pdfText.Substring(afterStartxref, endOfOffset - afterStartxref); + var xrefOffset = int.Parse(xrefOffsetStr, NumberStyles.None, CultureInfo.InvariantCulture); xrefOffset.ShouldBeGreaterThan(0, "xref byte offset should be positive"); // Seek to xref table and parse it @@ -93,24 +93,37 @@ public void pdf_xref_table_is_valid() string? subsectionLine; while ((subsectionLine = reader.ReadLine()) != null && subsectionLine != "trailer") { - var parts = subsectionLine.Trim().Split(' '); + var parts = subsectionLine.Split(' '); parts.Length.ShouldBe(2, $"Expected 'firstObj count' but got: {subsectionLine}"); - var firstObj = int.Parse(parts[0], CultureInfo.InvariantCulture); - var count = int.Parse(parts[1], CultureInfo.InvariantCulture); + var firstObj = int.Parse(parts[0], NumberStyles.None, CultureInfo.InvariantCulture); + firstObj.ShouldBe(0); + var count = int.Parse(parts[1], NumberStyles.None, CultureInfo.InvariantCulture); for (int i = 0; i < count; i++) { // Each entry: "NNNNNNNNNN GGGGG f\r\n" or "NNNNNNNNNN GGGGG n\r\n" var entry = reader.ReadLine(); entry.ShouldNotBeNull(); - var entryParts = entry!.Trim().Split(' '); + entry.Length.ShouldBe(18); + var entryParts = entry.Split(' '); entryParts.Length.ShouldBe(3, $"Expected 'offset gen type' but got: {entry}"); - var offset = long.Parse(entryParts[0], CultureInfo.InvariantCulture); + var offset = long.Parse(entryParts[0], NumberStyles.None, CultureInfo.InvariantCulture); + var generation = int.Parse(entryParts[1], NumberStyles.None, CultureInfo.InvariantCulture); var type = entryParts[2]; type.ShouldBeOneOf("n", "f"); if (type == "n") - objectOffsets[firstObj + i] = offset; + { + generation.ShouldBe(0, $"Expected generation 0 for in-use object but got {generation}"); + objectOffsets[i] = offset; + } + else + { + // Free objects should only be listed for the first object in the subsection + i.ShouldBe(0); + offset.ShouldBe(0); + generation.ShouldBe(65535, $"Expected generation 65535 for free object but got {generation}"); + } } } From df768dab0ff9b168e3593035bf380c8abfbab4df Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 21 Feb 2026 12:09:00 -0500 Subject: [PATCH 9/9] Update --- QRCoderTests/PdfByteQRCodeRendererTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/QRCoderTests/PdfByteQRCodeRendererTests.cs b/QRCoderTests/PdfByteQRCodeRendererTests.cs index 86ea5d47..ad02b27c 100644 --- a/QRCoderTests/PdfByteQRCodeRendererTests.cs +++ b/QRCoderTests/PdfByteQRCodeRendererTests.cs @@ -65,6 +65,10 @@ public void pdf_xref_table_is_valid() // Parse from the end to find startxref var pdfText = Encoding.ASCII.GetString(pdfBytes); + // Verify no \n line breaks; only \r\n should be used (this test file has no binary image data) + pdfText.Replace("\r\n", "CRLF").ShouldNotContain('\n', "PDF should not contain LF line breaks; only CRLF should be used"); + pdfText.Replace("\r\n", "CRLF").ShouldNotContain('\r', "PDF should not contain CR line breaks; only CRLF should be used"); + // Find %%EOF at the end, then work backward to find startxref var eofIndex = pdfText.LastIndexOf("%%EOF", StringComparison.Ordinal); eofIndex.ShouldBeGreaterThan(0, "%%EOF not found");