From d16d3fb618892592d00940a8f6b61f3de884745f Mon Sep 17 00:00:00 2001 From: Christoph Schober Date: Thu, 29 Jan 2026 17:05:33 +0100 Subject: [PATCH] Feature: support split documents based on tag barcodes (#11645) --- docs/advanced_usage.md | 21 ++ docs/configuration.md | 14 + paperless.conf.example | 1 + src-ui/src/app/data/paperless-config.ts | 8 + src/documents/barcodes.py | 41 ++- .../samples/barcodes/split-by-tag-basic.pdf | 191 +++++++++++++ .../barcodes/split-by-tag-mixed-asn.pdf | 251 ++++++++++++++++++ .../split-by-tag-multiple-per-page.pdf | 181 +++++++++++++ src/documents/tests/test_api_app_config.py | 1 + src/documents/tests/test_barcodes.py | 189 +++++++++++++ src/paperless/config.py | 4 + ...licationconfiguration_barcode_tag_split.py | 21 ++ src/paperless/models.py | 6 + src/paperless/settings.py | 4 + 14 files changed, 927 insertions(+), 6 deletions(-) create mode 100644 src/documents/tests/samples/barcodes/split-by-tag-basic.pdf create mode 100644 src/documents/tests/samples/barcodes/split-by-tag-mixed-asn.pdf create mode 100644 src/documents/tests/samples/barcodes/split-by-tag-multiple-per-page.pdf create mode 100644 src/paperless/migrations/0006_applicationconfiguration_barcode_tag_split.py diff --git a/docs/advanced_usage.md b/docs/advanced_usage.md index 89e076167..638544005 100644 --- a/docs/advanced_usage.md +++ b/docs/advanced_usage.md @@ -805,6 +805,27 @@ See the relevant settings [`PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE`](configuratio and [`PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING`](configuration.md#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING) for more information. +#### Splitting on Tag Barcodes + +By default, tag barcodes only assign tags to documents without splitting them. However, +you can enable document splitting on tag barcodes by setting +[`PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT`](configuration.md#PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT) +to `true`. + +When enabled, documents will be split at pages containing tag barcodes, similar to how +ASN barcodes work. Key features: + +- The page with the tag barcode is **retained** in the resulting document +- **Each split document extracts its own tags** - only tags on pages within that document are assigned +- Multiple tag barcodes can trigger multiple splits in the same document +- Works seamlessly with ASN barcodes - each split document gets its own ASN and tags + +This is useful for batch scanning where you place tag barcode pages between different +documents to both separate and categorize them in a single operation. + +**Example:** A 6-page scan with TAG:invoice on page 3 and TAG:receipt on page 5 will create +three documents: pages 1-2 (no tags), pages 3-4 (tagged "invoice"), and pages 5-6 (tagged "receipt"). + ## Automatic collation of double-sided documents {#collate} !!! note diff --git a/docs/configuration.md b/docs/configuration.md index ef252ad4a..32d56408f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1557,6 +1557,20 @@ assigns or creates tags if a properly formatted barcode is detected. Please refer to the Python regex documentation for more information. +#### [`PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT=`](#PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT) {#PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT} + +: Enables splitting of documents on tag barcodes, similar to how ASN barcodes work. + + When enabled, documents will be split into separate PDFs at pages containing + tag barcodes that match the configured `PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING` + patterns. The page with the tag barcode will be retained in the new document. + + Each split document will have the detected tags assigned to it. + + This only has an effect if `PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE` is also enabled. + + Defaults to false. + ## Audit Trail #### [`PAPERLESS_AUDIT_LOG_ENABLED=`](#PAPERLESS_AUDIT_LOG_ENABLED) {#PAPERLESS_AUDIT_LOG_ENABLED} diff --git a/paperless.conf.example b/paperless.conf.example index 424f6cce9..9974aeab6 100644 --- a/paperless.conf.example +++ b/paperless.conf.example @@ -66,6 +66,7 @@ #PAPERLESS_CONSUMER_BARCODE_DPI=300 #PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE=false #PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING={"TAG:(.*)": "\\g<1>"} +#PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT=false #PAPERLESS_CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED=false #PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_SUBDIR_NAME=double-sided #PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_TIFF_SUPPORT=false diff --git a/src-ui/src/app/data/paperless-config.ts b/src-ui/src/app/data/paperless-config.ts index fd151002d..ce4faff81 100644 --- a/src-ui/src/app/data/paperless-config.ts +++ b/src-ui/src/app/data/paperless-config.ts @@ -271,6 +271,13 @@ export const PaperlessConfigOptions: ConfigOption[] = [ config_key: 'PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING', category: ConfigCategory.Barcode, }, + { + key: 'barcode_tag_split', + title: $localize`Split on Tag Barcodes`, + type: ConfigOptionType.Boolean, + config_key: 'PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT', + category: ConfigCategory.Barcode, + }, { key: 'ai_enabled', title: $localize`AI Enabled`, @@ -352,6 +359,7 @@ export interface PaperlessConfig extends ObjectWithId { barcode_max_pages: number barcode_enable_tag: boolean barcode_tag_mapping: object + barcode_tag_split: boolean ai_enabled: boolean llm_embedding_backend: string llm_embedding_model: string diff --git a/src/documents/barcodes.py b/src/documents/barcodes.py index f3c693edf..d435e2956 100644 --- a/src/documents/barcodes.py +++ b/src/documents/barcodes.py @@ -60,6 +60,20 @@ class Barcode: """ return self.value.startswith(self.settings.barcode_asn_prefix) + @property + def is_tag(self) -> bool: + """ + Returns True if the barcode value matches any configured tag mapping pattern, + False otherwise. + + Note: This does NOT exclude ASN or separator barcodes - they can also be used + as tags if they match a tag mapping pattern (e.g., {"ASN12.*": "JOHN"}). + """ + for regex in self.settings.barcode_tag_mapping: + if re.match(regex, self.value, flags=re.IGNORECASE): + return True + return False + class BarcodePlugin(ConsumeTaskPlugin): NAME: str = "BarcodePlugin" @@ -126,8 +140,14 @@ class BarcodePlugin(ConsumeTaskPlugin): self.detect() # try reading tags from barcodes + # If tag splitting is enabled, skip this on the original document - let each split document extract its own tags + # However, if we're processing a split document (original_path is set), extract tags if ( self.settings.barcode_enable_tag + and ( + not self.settings.barcode_tag_split + or self.input_doc.original_path is not None + ) and (tags := self.tags) is not None and len(tags) > 0 ): @@ -432,15 +452,24 @@ class BarcodePlugin(ConsumeTaskPlugin): for bc in self.barcodes if bc.is_separator and (not retain or (retain and bc.page > 0)) } # as below, dont include the first page if retain is enabled - if not self.settings.barcode_enable_asn: - return separator_pages # add the page numbers of the ASN barcodes # (except for first page, that might lead to infinite loops). - return { - **separator_pages, - **{bc.page: True for bc in self.barcodes if bc.is_asn and bc.page != 0}, - } + if self.settings.barcode_enable_asn: + separator_pages = { + **separator_pages, + **{bc.page: True for bc in self.barcodes if bc.is_asn and bc.page != 0}, + } + + # add the page numbers of the TAG barcodes if splitting is enabled + # (except for first page, that might lead to infinite loops). + if self.settings.barcode_tag_split and self.settings.barcode_enable_tag: + separator_pages = { + **separator_pages, + **{bc.page: True for bc in self.barcodes if bc.is_tag and bc.page != 0}, + } + + return separator_pages def separate_pages(self, pages_to_split_on: dict[int, bool]) -> list[Path]: """ diff --git a/src/documents/tests/samples/barcodes/split-by-tag-basic.pdf b/src/documents/tests/samples/barcodes/split-by-tag-basic.pdf new file mode 100644 index 000000000..e7959fab9 --- /dev/null +++ b/src/documents/tests/samples/barcodes/split-by-tag-basic.pdf @@ -0,0 +1,191 @@ +%PDF-1.3 +%東京 ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2 4 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/Contents 15 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +4 0 obj +<< +/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font +>> +endobj +5 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 290 /Length 3461 /Subtype /Image + /Type /XObject /Width 290 +>> +stream +Gb"0M0bW:r$j4o4s3aL9.o/:sRKC1+V[Po_hnP="8Wk>jOsEV^,Y=.E8Wk>jOsEV^,Y=.E8Wk>jOsEV^,Y=.E8Wk>jOsEV^,Y=.EM=P;M`ictLh5:'u?=R'nd;IE]5Hh=BmqB2p^7X$0Q$9UiFPkD[m)hEDD7]3!20S(%m5Eepo,>73Ncpo[qg"0,Gt5J@p\hbEY.UOcVYbgK@oqO7DN/KYR@egT:O\Y"S^Nmne(=m!@M;\.mreAj`lssm2RjQmR*'f[]=0V/jtsN_^"C8&k'PptV(jd(Ymp-?-DiQUlg??aR5p7DE%a+(Q2+a1De[G>Bl&EKZ&,I(pUY]E@qJJG)r-?G9P(rih-1dREuNfk?>O(#o=aSKd[6HOfEV(Z'2t=fFn_3AbT-'E'3sZfj1^M@pYhQ7E1%B!q_i'CLMJZ]APP)MgR*7.Y/pg53RP?TA*/3L-50YH7,u"@RJ5[/9Q6C5NVbVGhM5l%_.?@umb=+S+0N]gQTFE$MV_[_Y246E[o=\bnb0967Q$FISai'U8mksuCAo?M*bkl?R-I0h_YM$B?F8J^DhM5l%EG"?[c+]I2gNP.=5$X;.1Gdp(p8uQo^/LHoiL3GZR2\raAeSG3ICLU;>is%i\_.+PGos32"IH[hA8X4IM[5E1-IZRS7[g)c,U.'3s[J\0_kok/NUqf`[Xe+0N]gQauWsDDo=BhM5l%_.@LHR@?oiRJ5[/9Q6C=:Zc7&>ipIE-50YH`fmsd"IFD+`\u,TrPk$]NL;edD'YO[I2buE1hPl,[ZP+_p2)p[e!QQPfLD$lgUH]`:1Im2@iJ!ODVrHt3K9FeNGTr/\U>Dmjtp]41q&NWk4WXSRF@Oke(@-QRG54@A56WH:1G57Ao?MGP<"Vj3K7l$RCR_b:ZaKGk$5CC.+u(L[aFc^qb6b_]O]p>fgaTjmPE\no9+M@B,b.F]?bTVcV*tKS8EA]mlo3K5;1^!EOO9f^ACUurOc[u`n=fDB;AhhFPj09^)KAJ38&9VV?L8MpH&M<8.ldJV05RX^_no.Ld9qHZg`b02a-m$D"%2.\6nf;,`[G2:]5WQ\V2c@4Gh=&YtOF%n^mA_13^REE`2l0OaBG;Wq]1Y8G/?Zt8UPc;l3PKnX1F]VM=136/NqdnAb9ps/J2L9Q0S)iGGeB)@_DF)%_Cm',a;^\2o]*8-oZUsS%9V$PXmM>H\bU0m00m3&T\6I=`1RmI^`mi+Cibh&sc>8Yj)cJ,VM7Wri3jVEGD+pLJ-LMZAlc^]d[kW$rRCHJJs-pTpHZ/#)PI^.2/_/Gu9ldRQT$2WWCT5#pBp+rKo47:$?VC&L8X%rrR4!(5rE?5)8Xe^PcTIWmmak?b:!t:GHfiH*GJBI/CQ^$TfeZFd^AG<;?^!=gc(929pYE$LqO43ODYD;<\aOu!e^l'@EjKDMb^K5$WP0]nZLJ^%HY#u61\3[enc_V2NHb$M)gp)%RGYQ;01^D,]VFZHi02I1r6C:L6.0i7*Bj-$T6+]-GAcILP+EW]kd`YIUbagAF!G%Ro\=[]cb7.BSXK;E)u5)]kJfT0mL;AEbfoP2a;6*b2r;r'Dt$>2Aq&o4^*)[NnW'2fK24Nao/eo%"\I%"GP'Z0I+"FNhmnk&i,4+8D*45U9lOt5(Y:H%gNYJ4S)E#I0RZO*cDrSi.cAfFP.AeSDgqSi-Obr20;bpKqYoS`%'Rr(9URn[j=kSMi,2qrR42k/aZcu)c8-TRTajWn-2h:0V>:?H.K8QTXcol?4Z\QM\UQ.esGSE+3uQBQEeG#L%A3LQAu,[ID*eB:EYk%6VF=)'\eEfuWs=\dD1g.f8NjCE.oPBA92cbB:e#8i1-tFRQ=g8G;/Vi_h'@1H2o>ZY116t>@KpYJMpA7)Ji/leW#F/+)#V*VC?f+jW%d?qJl]slE4fpD#^99j27h!!U!Boq])FiC1L1hLXTfG0bKqW+XE:1[0q:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9j"6dh3D"AU^/LHoRCY]P]2P,]<+kV\Q$K"$)s"^pPrVEYk.Xc^pR/TYm^lDcP>l2_4-b)`W>jp44-_ftFlpD:RJ3,\612?`R?LT_mQ6\ZT;`dj^,qT?8Tj10;jmBJ\j>br;jihKBC7jHH(V&TjM!^@3D"AU^/LHoRCY]P]2P,]<+kV\Q$K"$)s"`VbpKshhJ;heIJ#,$L#d]nmrG`@m^r4^I;<3g8o>f_?gbP]CkDQP]k60U=20o&8FDiA/iT9X^3d':\+\@Uj;*pUjhAp_-FiO$C\FlYoddS,jF4Z.EjH)?]D%bBCL@$4DBZPtm^q7jK)=uLB&DH`u,3p=6A-(6\RV^<=bJ\F89ip8rc9/%LApI_"ofZO-'3pR6MG?iVY"m@=Eq[a)Y.q"N1qoK.Z\e#:l3*)"BA[ObqR\dSisd?'T6oD_R;-aleNSse-CL'A2.`f0WDraO2OS)NhURji-Dsc/e(A2o3I+\)VOF#I[81:r8`o)>9poa:.b-_B9dZ9lG;Ws3af/8:1cCb4:>XNcW@"N@mF0]uOu[eh;l6"R9!qH)P=aot>tp`%E[oU'ND1afPBSlqWl_5>q`ONacB7HTe^^)FjdQ)cmKTR7qbD9Vk'+?_^P9A:.ET;&?(LdsY0!m+DK&4Rmo3A$I[=j@CUb=RP3b9\eX>=VRf")l#,`aD:3C^AGI]'8L:b8NahC\ZSbZQoafjZ@E([G)<**^]QYZ/-\/Us$loWbJRG[+pr#4u-V^2.7F`lhj\L&UoOsEV^,Y=.E8Wk>jOsEV^,Y=.E8Wk>jOsEV^,Y=.E8Wk>jOsEV^,Y=.E8Wk>j''C?8XL9P~>endstream +endobj +6 0 obj +<< +/Contents 16 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.1ec20b3a96e40a35266a0a8a0d634adb 5 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +7 0 obj +<< +/Contents 17 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +8 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 290 /Length 3500 /Subtype /Image + /Type /XObject /Width 290 +>> +stream +Gb"0M0s\bV$q&G/J($uOju)%+%1&)hn*)-NT`"8nzzzzzzzzzzzz!!'grT65NupmP_`]63jRce!oT8TqIFGMi(@D>9Q18%WpBeutHu8YIA4O7SpKc+sL9F0lZs.b3omCWORUeq#Fn]1ff7pJ#G-kItht;A6pmP_`]63jRce!oT8TqIF@N3&*FmnO#.ck97`6:E%D03I(`QWUU&i9D1[aFc>'f5%G8^-ObfLFJ>\r;M/@@JB>r)?I1e@5,du+nSeX'Eoh!BoPLr@VHWJ@\f-`;Z:LY8Kmo_Ad?D#0[5)F,u]k>=.H$p;]q^?Xu6pgRT0&;GI9.]Q(k==7(su_^mKeN$]^*XT/b_iRI^9\/Rk'_VO.X[X!?($+R'u%ohpmP1W1+Tpkqp$[=RJ65/WUOJ"FCk0:>qq.oTiPM!n,14O/tI1k<0>3<$5]2)lT?d&ATH1smHj(k't2X`iUD'W$A9g"p/H/Qm_8!9o-U&'oK;S+h0mmRk"Rt-k]u$5])/Y.baWi8dIY"AkBe/\3oGMcAUk_L);rMA#.X2i!H.gHJ/`tUi5T+.\FGmdDZ"(U:O*o%bOL")-=7^7DrE74n%8HFqc1)_qsI.l2X9/9=XrGe)G9:D2aqfB^S]TJQh-2j3jnnI0b'oU-pqAhRYDp-&E0eZ@h0kOd.U2CjG:$Z9F`64iQ1)?^./R#Qi;;q9^,G95_HAAGGP@N9hjJ)bTkmPnBP&2>q6mNRbVk[p.ML'C@j^(Kp6jTgZ9`&rR;L1/gVQ-1gJBf,9Jj)8R=&5kB4`+*#*k$W[Pk<$n'e`_=)ulnbsWAV8,n1Y\;=[tT6B[\7M6R:p1O1\nJ`cce;3%4W%9Cu];YgRj<\hRi@\^S#q;\'`3BG@'2DFDp_.g3E)3$iGVE:#8>Yn(i8??dQL.gM#W\4"p(2\i4mRD7k)U"b&c3-?#Z=p[5]00Bh9RD7&iiSJV&)h4)':2Vu(;!l(CTPIJrZHZrfS**lhrDLp#UDVjQe.sCQCb]ds]kIE*doS^q;DVjQe.sCQCb]ds]kIE*doS^q;DVjQe.sCQCb]ds]kIE*doS^q;DVjQe.sCQCb]ds]kIE*doS^q;DVjQe.sCQCb]dt()PtR=ZX#Ne^?[k]n7>Yqk"X@5,N$b[n+tfaEOuZ]=tTY?Y5"1hF(X2o%i[0Y4&I/QW`::2c81eHoLr:lT;0:AQJTg:"6Qqhp&n(qT^RT40.j^1..d;(9\b4QZdNohWf>5rbj0%"E=9M)9$`?o2DU%CYHQ'd/bh(O4X[8`a;i@8^*XN&i6/4oS>^0IF"$YVRS;Lg0=0)JU8j3sU!2h<13!]9bY$3.m58[g;k8V]Y5^+\)>H2oUMjp,BG:)qO1+5JhOIYF/#[obb<8HCGKl;^1-_DuEi6Cq2J1g9.'X4-oCLWfGBu>fA*2$m'&-5<5G.=`Vmk,5B&9%+Ymi#No@Ya?H95tRk08_qgJBXiTC$Zs\'m6IhOI,Nj6S(_kfW7]G@i>d]6GuK^GF1_VGb-dpCd3^o5%kcjh#ajEPFrts0m[M:a=eTY?+/Q$@*@YXq:#sL!:q!ThdT+nZPdC66nmtiM>M)I1WbY,IfmOP01+SS@m%\[Q[3Of"^576*(!7h)CHLZU3?^5"\m^4&XAlS&gq!Tkn-ZV5pa>F_*aPG"-*$7)!s@;;.u'G3*prREq=mOkD[UDr,o,2X7_Vq-@@iZY!i\p.aV;G9mdq<$L'BqoS#Q/D7G5&5=2B&?"jH1t1iW7uLWGC>n*R[oSo2j&%8I1k:217u7s3aHUt;K^6R.'Y9Ko@YXqe('1+7"Oc'p,d.jaf?/$.4ML+cQY]SR95;DOlX_E(t>pel7ZRjbNl-1fe?XOG^S03-W:M%[Eu17u7s3aHUt;K^6R.'Y9Ko@YXqe('1+apUs(p,X02DEh7SfseP+,u1V;r+DqE82-sb)nbWE/3Y5NP"]T:Lk9Zk"(]B\*gf?F9qzzzzzzzzzzzz5k,pqe-_`~>endstream +endobj +9 0 obj +<< +/Contents 18 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.1280b7d13f0587f75dbba24117c3e12a 8 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +10 0 obj +<< +/Contents 19 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +11 0 obj +<< +/Contents 20 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +12 0 obj +<< +/PageMode /UseNone /Pages 14 0 R /Type /Catalog +>> +endobj +13 0 obj +<< +/Author (anonymous) /CreationDate (D:20251216142815+00'00') /Creator (anonymous) /Keywords () /ModDate (D:20251216142815+00'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (unspecified) /Title (untitled) /Trapped /False +>> +endobj +14 0 obj +<< +/Count 6 /Kids [ 3 0 R 6 0 R 7 0 R 9 0 R 10 0 R 11 0 R ] /Type /Pages +>> +endobj +15 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 180 +>> +stream +GarW2_$YcZ&4#]5`B.*sm?lG7-r1F@qZF9DA`.IipA]su.E!o]Gjlb!W-Y8MVLYd\SdH+*Db)WVcj08*lF:SRr1h[EQATgu\mTX&mM6\8TBJjRrY+[@;2SB>n%endstream +endobj +16 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 247 +>> +stream +GarW49oHkR%#46B/)I%IW6)D&Ib[t)(Vt`^F7>Vna$l[3>H5Gfa')=U#ta'/k95LEWOSG3"r7^/'n\C!!F0.=g]gEL*]\ARI\tm.$t5=QeJN,a-U84$:2lt0pl4os1D+HLYKtgFoDtT3?g;TJZ"p1Ms8-mnFZu9hhtp+9>=dda($9HW?h<;`raPb)D!6nUI04P3H\NHB@T2`*R;q_bb$8O:'I:6:edBMuW3U[lh[8WZ3d+9:~>endstream +endobj +17 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 174 +>> +stream +GarW05mkIo$q9nR`F#VQ:"lUo>6R=/74qEVW"iqLlKJ$aC5XZ78-0=Y@o+._YHbl4Yh1ZiKj;Fh4GOo\Q)*Tmo(5G_$S2VM\7.0Qendstream +endobj +18 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 244 +>> +stream +GarW4b6l*?&4Q?hMRuh(23Tpmkt^cRET&,M[qV:g6EQRMs2I6!U^T$](X?G+!r#.NZmDs+Qu<6UTF4R17*n#s2&gSmk2Mk.09@0r[k9DgRu9WjKt]k!Ic1n'J.q+%HiE61]07.7.f1^O]tp[&Fn>&8hUMD]+;spqhR>>LX`Bap0=GTNaa\,&7Zqt_h]FP:T4bD(VQ.g\Pm&EGSuJ6sK$-Os9'08g6q!hekXZP?.6B5f2_iendstream +endobj +19 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 177 +>> +stream +GarW05mkI_&4Q=V`F#VQSZ/`o[HbngaBTj`WC0EX,i7E\>AQT[REn!?T0[2$]@m:4_endstream +endobj +20 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 167 +>> +stream +GarWpYmu@>'Lq&PV`:G4BJ1Rt7[P0S\M$3?0c4(6nGPe/#`385LHkTp@/;7gD`sVT>qS[,<_0MjcjQ72dr'n'riIp[%YeJCl<.DN]-CUV%s0VJJ"dSm@n9+>F4SmfNcSuChM%!&%Rn8_]8SB3ren*ZdT2U-SH5HD>?EhY~>endstream +endobj +xref +0 21 +0000000000 65535 f +0000000061 00000 n +0000000102 00000 n +0000000209 00000 n +0000000404 00000 n +0000000516 00000 n +0000004168 00000 n +0000004426 00000 n +0000004621 00000 n +0000008312 00000 n +0000008570 00000 n +0000008766 00000 n +0000008962 00000 n +0000009032 00000 n +0000009294 00000 n +0000009386 00000 n +0000009657 00000 n +0000009995 00000 n +0000010260 00000 n +0000010595 00000 n +0000010863 00000 n +trailer +<< +/ID +[<93a746516153ebd4bfbd42147dac7019><93a746516153ebd4bfbd42147dac7019>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 13 0 R +/Root 12 0 R +/Size 21 +>> +startxref +11121 +%%EOF diff --git a/src/documents/tests/samples/barcodes/split-by-tag-mixed-asn.pdf b/src/documents/tests/samples/barcodes/split-by-tag-mixed-asn.pdf new file mode 100644 index 000000000..8bdca9ffc --- /dev/null +++ b/src/documents/tests/samples/barcodes/split-by-tag-mixed-asn.pdf @@ -0,0 +1,251 @@ +%PDF-1.3 +%東京 ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2 3 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font +>> +endobj +4 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 290 /Length 3565 /Subtype /Image + /Type /XObject /Width 290 +>> +stream +Gb"0M0s\bV$q&G/J(%!d.nqi3%"dr>?4mOts8EB+zzzzzzzzzzzz!*oM+-e25^Cnl29l&b?1]i8(4Z]3i/UdM`^pJP:nc8L!XP7Hikhb/*W3nrlS:-,3JIP95Ol>35>46_jpqn5s1WO%T@mA*+\n+T^cV9UuF\!%DS+Prd[`h+g#,qgL3K&Nmea[s=/iNchX.++@iJ&&?TXb*R+\CBNRSd!GW)BQZVP'MW@t^MCeA2LT>ice?YncUPfCp2NGYK*fZ(6HNL>25gIQatNFu0.1]'#;OnrN+c_`O2p1\=fou/h\9khiWj?]th(k*4B.GJ2o'A_tJZUr&HY,rP-sml,W^p[L,?n7>?dc$S(>%:A7GHqc)2gh%fnMR<8APPhAHe8-(?flca9e(OY;c<5DomXfFGopCWMT3kt`d*!#p[L,?B[!PkSCa*Q+((4h`ls=[H$u+TnMR:ZbcGKJf9^b)?^$Aeb_jDcI@9j"25t95XD"-UQ['Z[PD[B(MT0piRJ65/WG'MWBUm\@GopCWMT3kt`d*!#p[L,?B[!PkSCa*QT>id:gK2Uh2l/78:Lb>9k4;`pb^!,*1]'#;Tk$?Z9k4;`pb^!,*1]+i^b,/"tD*6S:4RYJu^>DlOD*6S:4RYJu^>DlOD*6S:4RYJu^>DlOD*6S:4RYJu^>DlOD*6S:4RYJu^>DlOD*6S:4RYJu^>DlOD*6S:4RYJu^>DlOD*6S:4RYJu^>DlOD*6S:4RYJu^>DlOD*6S:4>5EnIDCiW$uk5["5N[p!&sm4^l?);E8rSRh"]%m2uYSJNP>nZ4+DCq2sL42I@5NP.]F?D#Lof[P5%gF/jYQ2SRj89<,>J(1q`4::6/2`]"iN8Tg?Q_T]1.WV03Cpgta&WQnCGR+^"MeNQSEhe])7eQHVp1V5]Gg.,l;RuOeN3_O+'E+BLNdNcEVXe;i3RP$%mDsKm@k(jJR1oOfe;eo82l?.WSiP3%8aXTCF(6\,k*Zn]Rad@F2`IW)AY(morkEmYot@DJjn#L]gNe59mc3'j^2HC'b3)cC\+BIQh8d"^IG]e-Q-ZMfDWg]+]8pmFqn5G9>^kpO*uQ=$Eg6]:IRJmQSA`me\o,Y'aRKFuunrh0j\Jh=kb7?2;.um<2gn]DMBs]C4KN](1-sg[2YfGPCXpGMfm'FQN+o\(c1Vn*]5jn%N^-l,r0hDn#?7hJLcP6!rcB2`JI/^Lm3;l:\Y34S.^TGOa;/SmQmP\!^mch"WN$Rb@;50B"h\baTR/`EtEJIe>#BAtF9hj1G)%B$[ZpK\O^,Df0q=J'I?UeX(6EH03A2n)CO=4hph+DDTVO\a`u'27)II?c$[BR5[(=Mn`ltr9?qbbrkU[`cn]Bb\tDn[ATgh_uqm2NGlQ/0;Y,U">dfPDnc-&V4,=*1V\of^AKX\R`#=b^Q!0Gr%Fn8_uqm2NGlQ/0;Y,U">dfPDnc-&V4,=*1V\of^AKX\R`#=b^Q!0Gr%Fn8_uqm2NGlQ/0;Y,-.D:`8P^-25mE:I'Hs9-=k"U!+0/NUuD';?92q?HmbkTAjKbS+T<*/rPj"K:*;NuH*?1#go^O06V;k2Y&`)n7A2:Z_HtqV9)3@-5F8=1N-;N^8j*DaOd6)Q[.`ANgrQrLKeIMI%HX\.QT920,N"Lq^240*VXc-(AOhjpaO%4/@LXQ!ME0B[2"GG`AEo+f[B6sbm`mc9m#]q`O#$/_S\InlY.6XO1]m^XzzzzzzzzzzzzDZ'feLL1)~>endstream +endobj +5 0 obj +<< +/Contents 19 0 R /MediaBox [ 0 0 612 792 ] /Parent 18 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.969a0a278dab8164403e924542d002c7 4 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +6 0 obj +<< +/Contents 20 0 R /MediaBox [ 0 0 612 792 ] /Parent 18 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +7 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 290 /Length 3730 /Subtype /Image + /Type /XObject /Width 290 +>> +stream +Gb"0M_/\Be$q&G0^ZfPD]&P!ICnq2'o`LkpkCLkpkCLkpkCLkpkCLkpkCLkpkCLkpkCLkpkCLkpkCLkpkCLkpkCLkpkCLkpkCLkpkCLkpkCLkpkC#aKo*\@Q^OSZ$ER1V!=bkN^34G+uR=bi%p$SuXBnfno!;15!4-HEODUB.'_6aUIUOlaaTISXl]s-Sr:0Y35)mF`sLjS]p$[]:Q&Rf@c#GFu"]^ms;.>>\>FagW9ll^?`>@Eh/c"DC76Mql],Ib&qm%\t.&!Z&R?j^GbOE'AIa`o5!sc1U2qk1;>a94ql[q/bGV/N&oBnE>NRor#2X`gCgNY=AC\-sMZ&R?j^GbOE'AIa`o5!scUY@ukb,dZtkV?'BplZgKBG'"Z:>Y'KJ^^S/i`O02&@QeB>8mp5B@FIDmN2+d!e]=@":lhuPjo00`4$!h-Zf=Z=@hp>rk8(T#[bO"5WpTF60]$1'8RO!!C?!R!XomT8pGop=UKJ'ThCYl\[Ds/tFUMH%>ATi(G'"Z:MgZY'JQ$FHWPCtQ0cSg*HEb4)tf-I'5gpU[B.@!FI<80r]$*=lkaBSYGop=UKJ%>l2L;6@A_q'/)nO7;hq=!\oBK#nX>@*jfCqu2S6'?4At=cq\@uUMkI`hh1[@_N_t?/+4m1@`Qs"'2)m8XtkW,il`6:7UgMhSe7*g.$->(hsfS8^=-@3t<1GQ5]`&:lX1XU8MSV`bW-FVn0Sr''1l:fF'1U.D@pIt4L=(buSS\"9g$Sq1hZP/nmh8e>q4YlKEZP/nmh8e>q4YlKEZP/nmh8e>q4YlKEZP/nmh8e>q4YlKEZP/nmh8e>q4YlKEZP/nmh8e>q4YlKEZP/nmh8e>q4YlKEZP/nmh8e>q4YlKEZP/nmh8e>q4YlKEZP/nmNTnOXoArC<7fK\![JXSJCAH$seFJqpW9#[j:s32]TpEGE67m[iKNeA\pj;0WLN'q"ON]k7-=$$-j4_68?%QVTfe_T5G9>H1FmfqgF3gEaf=Upi[]b%E+IRTM9Xrs8a0g?O]*-72ZY./JmQRf@leWpYk+4^LY'9V[D.!#i5r/3$R;fjOO)+X(G6sGCB?u7sh0iP_f=ah=bWU;">jq+@g:lu\Jn4<&1Z[2I)[Eg"5iOQZRkmX#m@t_j,M>I6HJ98N4`&]!Jt+>ab?>fCRgbFG%6,40*/7Q$K!M4l:ZZ2orVBZar&"RJ5CdoA,aTK=n6HX)#V[:@slSb$oB%P_-c4fU*lDeT+\#EUk]?^+lSnK3I=&o./:$&t>Fge4>7/OL*WnQ'A.bDqJZTXj,g;h`De\5p"\ScS]mHHa\5-D&gp2ukIFgLCM:ZmT.EHP9hYM3$ZRpBZR3`!,DX/i7Kh555KloNnMH(o^X)nIACmca(3]&?8XH^EO#&*`Ae%A$K1Y#MuVaj'^EY$Tl@3>LLFV0eQa]FX8l[1/^+&[#*EGKJ,,OKMpEi&MP28u6m$gq40^lEq(m;\?03\p)9@qiIsegYb(,iC]qnQe]4al0>YcSl66(<4[:qc23U*?JFBu(#_c]H^EO#&*`Ae%A$K1Y#MuVaj'_TgceTc9^P6Z<$X7[40]52>oU?qkq_QOd\S.Uak;Mf\EMYnm7+^7iM(*T[j,0jh*ehR,VqGBYmhLT)nI'S1@$:8V';>LfnjRahf+!d4Yr:$&RTWeXR:GdAL=B#^?E:5h=n"o9N"<,d=&:2moc!FUl+rAqdO^*ZS"%*(%F`e`Tk0M?"T6Lif!5_eU>cTbTp3,(Yon.qjqqu8ZIS"\h@+Y4mOke<"WJZ?``[Ben$2-iqLN\dhA/$Fk1]AleV0Wqc%F.%l1?SXQsKM]B>[XWS0lbA7!kjf?LIBcSt;k[?3Yo]WSTDRpGKSDr.C,,CPZ!?"[G%]A>^.]MI'IqB5C@cFRn:]mIJ=T;mnC60<<\FkuD1pO)Qr8CoXP0f)Bi\2(\YSOh?g0ekIU7CY+bk+,&p1GLU?8O^-E]B5YTf/#-91J($F1:u\>8CoXP0f)Bi\2(\YSOh?g0ekIU7CY+bk+,&p1GLU?8O^-E]B5YTf/',Dm$``@G,6D((A8nW\TUBd\gAVh?)\+8KmcKnXrWY2Y),\]="8f<@3829p8`OAQe?M&cZ_r1h7Gh42TU>in*2[?DXR#HV?"\:bWHu##e&8C[B%L0+=/qj/oJEg'P1atYa-EZ;)a-p^MI^n$eT%GNfg\aN]-OFjh.acpEIVr$H!NAt6b;R)2(s6aDP*3c\o-A[FOcRj?Li!i4tGf=h7O9_W[HE2jP@Cq6N>k=,&lh4IAYNt>!867sbjsBKImB^h>f?O2SY'P-59AZf*BB?e/S+e(3p#MR.]N_kkb'@8O7moa56psF26psF26psF26psF26psF26psF26psF26psF26psF26psF26psF26psF26psF26psF26psF26psEGnK3us@Di~>endstream +endobj +8 0 obj +<< +/Contents 21 0 R /MediaBox [ 0 0 612 792 ] /Parent 18 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.f6e745526a61cd8416256ca62679b160 7 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +9 0 obj +<< +/Contents 22 0 R /MediaBox [ 0 0 612 792 ] /Parent 18 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +10 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 290 /Length 3461 /Subtype /Image + /Type /XObject /Width 290 +>> +stream +Gb"0M5n_s,&44e"s3f#4`]d?gQe*Pf:j1Mf#9(fYt0GMj\u/gIQatMe>s(1]&`3OnrN'c_`7*p/u2Vou/PT9j,^,Ze.b4Md8H7ZC'F.^O'MkF4Ohqqb?,=QF6qiQIaSReL+VDtl*Fa4I-(QT6?Ln%7H6n7B=pY=XF$F%U3(GJ1cMbBtC=P6t[ZPgmA7H@Ni!q*)M>'u%m@WHmCore<)tI=+a=I$mo2o=jPh%:A8ppmP1W18gr1IFn?8^(1]&`3Tk&U9X6?'4AMLO;@N.r%kHg&kGuGJ7q^f1LA:?_CkaXSC0sf28)bT8UZ\m%R;\h1d/@M6_IQjgrS2Y"6Zd3)"9'5(1]*^>b$$4b/tQA@4R[^HI@mF]]6JMHo=XfhpO/Vgn)E"hch$!@h=l3;]SgD=4jSJIFPl71o.HV?oK;SjdX@acbe&+CdIY$F8b[Y)1@O5U7F1S^-f+NBb.NXF(Hp)kT6/uR.J#0`?1^8JIFm@j"CMipr`Xrr\D9K^C5)A0*C#)b5aL#1AlXJ?_-a^qS$TLB&C3mWl91IT'qls;.jLu]1T*BGEncgPM/lHB&C3mWl91IT'qls;.jLu]1T*BGEncgPM/lHB&C3mWl91IT'qls;.jLu]1T*BGEncgPM/lHB&C3mWl91IT'qls;.jLu]1T*BGEncgPM/lHB&C3mWl91IT'qls;.jLu]1T*BGEncgPM2/fXD_E6B349YHfs51pA)9lSb:7.kj7TiI.+`&ffibEWO'jCk#HMHF`+p;hX,-OYH-/Uhn4ilY0P;dcF]Y7o6$uT3N5u9n)>&=(6k;:?VW*$ik%1h[ftl*hj9O![J2@LnSLS&g%XU(;D#V@;g_eGDYAK=WDnfEiY:8cPad).:+FfR]/[d="m'@RQ]k%Lpb#>3D*b_cpZ82K/\[-AHs9Ebp3es#th\K+SGuJ#)QPmPc>=_WR9m(ScB)M4:+[qeXe^])9n]SCq'P6-#=8-:G0AdOCP^-25h3T"oHs9-=k"Tu@?Y96,D';?92q?H,RISJ0KbS+T<*/g*`\63b;NuH*?/:hb^D<=2Aa]K]I&U"V^Lj`$?fpO@;dK2sV9XP7@TU:fcSqc5ntesLrL1^.I$pUF:3GYkPree"Md?-65Mr!rb*OT#p1kGUnn!9a3RsBJEr<2m32Ze1NroCL5+Y'd3HB>8&`.pqH%Ro$%9`G4KKHiQLSWk\FIH^>$c=6%ptuDkaFXebO)1hZah0beAqJelCnFSU;AUUb_hm.Q;i/o6l!*oPJ%,XE*pa1>g-]6/NFncRLl'/b=C<)t[uUnZ6d&f.u7([mAme(=+HhIkC8?HlM]p@I^F>o@GbTa1"@l()G4G2h*FNI=*"+D.2t9Eq@mg-]6/NHBZ\m7#^73Gke+.B(hZah0b_jn<^73Gke+.B(hZah0b_jn<^73Gke+.B(hZah0b_jn<^73Gke+.B(hZah0b_jn<^73Gke+.B(hZah0b_jn<^73Gke+.B(hZah0b_jn<^73Gke+.B(hZah0b_jn<^73Gke+.B(hZah0b_jn<^73Gke+.B(hZah0b_jn<^73Gke+.CS`ATcl]'#RI\URqX`ATcds*QUcpWr03fLG&tT5UUircro-gZ[Glo?Hp_5HiCB)kG`5^6tSikOhtoUN?=Y2q)jJ]KV6jFDh-SApWm;f4V@JcL!jR9[E\/qCK)&\c(]\l+):MT>`]L\,Uh34nm:gUO4#O;Y0and()GRhQKN)cYtuRad@F;Y0and()GRhQKNazzzzzzzzzzzz!'^D_bZ%t[~>endstream +endobj +11 0 obj +<< +/Contents 23 0 R /MediaBox [ 0 0 612 792 ] /Parent 18 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.e28945bebb594f11c83bbac90aba8c31 10 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +12 0 obj +<< +/Contents 24 0 R /MediaBox [ 0 0 612 792 ] /Parent 18 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +13 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 290 /Length 3621 /Subtype /Image + /Type /XObject /Width 290 +>> +stream +Gb"0M0s^+9$q&Fqs/,gl02gI,?@ebho'Q]Jl/07UWiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiDfkGE/^Sf@`Q543?FKH_.8gf@`Q543?FKH_.8gf@`Q543?FKH_.8gf@`Q543?FKH_.8gf@`Q543?FKH_.8gf@`Q543?FKH_.8gf@`Q543?FKH_.8gf@`Q543?FKH_.8gf@`Q543?FKH_.8gf@`Q543ipCC-5Bct(Y?=Wp&5CQo0$?B$8/ZCCYnN+rs"hc*j/'8mruHpK?JWQf;+#Am,FK.]AC<=A\Nq8;ScpfER"Adn)*%Yhnq=Ni%hpSk5DIWDfD-5iL3_bR9a(hIb4HU\BO\d9e*Zs0!9K7lhqc-k'OTb'oA=nM=YbUn)9^7,\C(&cHBe5LMKeMQs#P20:2JUY";&kAjt^l>ipCC-5DB*'oDO&RIB+'<$Z'V,gOSE^5$K%OdK:63%pWo*j/('@V/'g]NjTG\0_rIR2(SMEgcCTRT(PE<[>u6A\KOs%;u'#Sb'E::1>/7k3'(.Sb'E;RdE]tk]1T*AGEnbLc."ZGm"eT\9[DDGII>0Njr'D=Vku4qWj\h^:>I@'mJ5_S%&BCB:Ei-LeG^XFlDJb^Y[Z+RlfV-EG&ReH0_YX[e]'G\5_6d(N/++l^_n,\K`^AGLhJ;\,L\dRr@_N9?!HEiT@8WhV%%\45s.XB3i#mlZ;O]qHH$/Ei%K8ZdQjs;0Kn23KYiBcC:(WIZ:WXcH_8OWHR."[..pr*H?6tj@ZiOAu1JoU[3fCF!=\cSMr'9r#0-:S=L>(;XM,"C#den46#UsaH^N'bt8qi7bJAdklE\XNG6UN8LphGmJ@4M14IHaoZ4ZeUS*GEl,hcgJIA*j0/;/@Qkc>p\%HZTE*>L[/)'bO%iG=%Tgkm*;*hiHePME][1UY'\l*6G@jY0C3"'YZ]r[+)[^I,`B1E(E&BY`bumYNV%!SeOlDqb;n/9dg?'q4ZdOS[Rn3(plmZ16:2P'Basg/)`%.GgIUKr`=pD9c$7.r?]EM%oF&T#0%=e2CqgR?]ZEgdI$DAl%B#Eb_)MPfu`=$>q',40t93l`5*_bh8Da2V@gYE&Vi]VQ()KrX7N+S=QWoK:W#DG;ElEWnLkD^VJ[LfpSiuf_I1>2fj/>WXX.N$f'!sSZ-@VSc8b81M:@#g0j*Y^tm5fRAr0tq(H\GF*=G78_NQ-gq8J&/+4sePah.#2X&$&Rs;:1Y*)Dfq&XF+0&4*r2_4>HWK`n@iATo:c'rsanue-J\bR:aK.brlM)QsR.]JZ%n)61g_TeQpZgt8^9"Fa=DM'Rs]SSQtmjb<$S+#;okebT6GdHtDP"N:\*c?T_gq8J&/+4sePah.#2X&$&Rs;:1Y*)Dfq4>O->a^k$GP6E&U7Dq/Eb2uZ:#;@QVo5`CRI>QfpEnTCYq&o9SJ&PTA\Rn5hOA2V`3*@5msUDo-JBoR.p(k[hrMJ-G"V,DQ208qH#KFJ'"`0q^@t@YRqO1PB*FWiRqV!0gtDND[j)'CR@@mQc.q"K*4I-rQ^O=VkKVf8qAcsh"dbXHB]mXqg%l\t,6aF%RqV!0gtDND[j)'CR@@mQc.q"K*4I-rQ^O=VkKVf8qAcsh"dbXHB]mXqg%l\t,6aF%RqV!0gtDND[j)'CR@@mQc.q"K*4I/H/+[u@c*aft?=b>Bh0TdDi&]&hZ#a5_,3@X-'"?d[bI+Tr?=b>Bh0TdDi&]&hZ#a5_,3@X-'"?d[bI+Tr?=b>Bh0TdDi&]&hZ#a5_,3@X-'"?d[bI+Tr?=b>Bh0TdDi&]&hZ#a5_,3@X-'"?d[bI+Tr?=b>Bh0TdDi&]&hZ#a5_,3@X-'"=O(9l,HWFN$+_O4)&'[O9tGf*4b0MJMc(V2`$&:VM1Z%?AjOfAo(e)fsc*I.pqd*2P0gaa971k-dGKm_(M<8lo8]pF'n&D0tj0HYm_dY.,hI;;.Dlp>ij[g>FAbED>;bZn]&Wa`m3]_,7f'R_2.)@rC%bn(qi4UNFI4H&pCngpSTf^"3HYl&'9IT#mbEg^]8if:NeP&#\sQciW4&pM9BP?]'^]3OKeP.2b)S)i;]hW6NEPf+S^h^$eXpF_fCVBkScq^/j3\9GL-2'm64gc*?__7e$'Xso2hikA!e%IRLSF%o6]&bf"?D/eraR;5FSNjs"jCP-u#b?Ru1mI-jpS(JIBR;;BF-sP=qR5_B$/i2,?Y>Ma4P;7&c='T@?a^:fZ@4,XC`3-Vo>a)olftpM,]STp6RCOYoNO"8/P^*qSi\GRLrYf>U4"9bJG/\%TRf#%c(3WU<:$db\bVf33PV)6tba[6"Q^MX[f-jkU8XVNB.kWX0A5uW0endstream +endobj +14 0 obj +<< +/Contents 25 0 R /MediaBox [ 0 0 612 792 ] /Parent 18 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.3d82fbc7fb155441d517c93ed9ec525d 13 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +15 0 obj +<< +/Contents 26 0 R /MediaBox [ 0 0 612 792 ] /Parent 18 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +16 0 obj +<< +/PageMode /UseNone /Pages 18 0 R /Type /Catalog +>> +endobj +17 0 obj +<< +/Author (anonymous) /CreationDate (D:20251216142815+00'00') /Creator (anonymous) /Keywords () /ModDate (D:20251216142815+00'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (unspecified) /Title (untitled) /Trapped /False +>> +endobj +18 0 obj +<< +/Count 8 /Kids [ 5 0 R 6 0 R 8 0 R 9 0 R 11 0 R 12 0 R 14 0 R 15 0 R ] /Type /Pages +>> +endobj +19 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 261 +>> +stream +GarW495=S`'SZ;W'k^[])"5]poB8qH=%/@qU>)&lKpEPq`^ToUj7jAr2hAmFc3IcS3]u1M!)nO?K#&(3$W`[Iq8D'E927i,D^h:d1ndTQ!o?uOgOi!M5;;R#n;G^&c9s)+r7(.`dIamA_=.6`V?\;0&JO?e+g!YX&8_+8XHlF=W/Ia'Xk$Qrk@3aA0PQh(F/A?*p4>/mR.MH!>TZ1)N#/`,?ZD:[7;^/hZ>/d(i^,*:I4^^bZ6b[:`%6eRJG9?mY#W4~>endstream +endobj +20 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 170 +>> +stream +GarWp5mr90&-_"(^Z$9?6d9G5GL7PscscphYcOP./#`K?ei9A^4+L=D=F(B57X3>:A[Gj.'WC58euaPELhe*k>#kjh012B&[7,F!'.Fi#)NkQ>n`2Yd~>endstream +endobj +21 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 242 +>> +stream +GarW44U]+\&;KrWMK`$TRZ#Ejh%W'h#K7+SBoIXGUUC)N@r5(2Nk9o[Sql>31)AEh'u8=l^EAVi3IoO'Kiqi%7%;@\=.b]qJX4AdRf].g4aIOJ0IN7E2uH2"iZoh[jaDCb(OO.Q\4>L2't0AZX'_QXW`mKcn@qdY_Po+mf_[DIA3%@^p+P5rOHsf;mM@n1YBPg%,*^&!jN2t$Q>3PS1:d?;-!$J*A]<=N-^O:%Yn7OC#!>ZE~>endstream +endobj +22 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 167 +>> +stream +GarW05mkI_&4Q=V`F#VQSZ,K\70UM]Mn-VGWC30XdbspQV83pXpp557*eT4-nNT1`#^08Rf8pba[K^cm7B44=jRnT)'K?Y!_o-'Xd$KGh6f*pL>lGBg3YUISI%0sL[)4K&0mV"rL1P41m3aEpO(Oendstream +endobj +23 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 262 +>> +stream +GarW4bA+pK&4Q?iMRuQnE&;l#G'=F5'q3C45Kp)<'Bl(2:?1Sq>(.;"Lm=KDjendstream +endobj +24 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 167 +>> +stream +GarW0YmS?5&-_rY`KY,2[kYm/Lmd;U4n)8L^>CNbM-K67PO4$pIc#YA[N="7;k`*bg6HG-3H1I:Oc@?^rETqVk2S`hAuD3XjG$hrY$;o0o!@8O*,PX[%Xbdo219TR[PYjYB"V@;M6R`QhNDP7g@'?7l*!B~>endstream +endobj +25 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 245 +>> +stream +GarW4]*cD?&4QKpMHd)Lgt?4sg)58p(5c>K-G(3oLqu4Qh_c`/,/_X?Kl\)c+FR*G9`ZLXdk-+bF:O1[#^VPk(.0^d>^]N8"Y:8_eceNuo\pFCTl8;ADtoB8^e%/BH9Q'/leU)Ketm'gGAhG8d]3*X.^/,jZXEc>aFc1K9PN;qHF6sh^&:Uu&B+.aq>DL)I_A*B[q/R0b:Eqd>ihB1o8K'3Lb)J@(88&BB3;G3Vi%7"GQK1T:[J~>endstream +endobj +26 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 172 +>> +stream +GapQh0E=F,0U\H3T\pNYT^QKk?tc>IP,;W#U1^23ihPEM_?CT3!/hd>6k,goQl7B?"*$l7Jjr"7HE)RNVF!h>6AQD_Ah8\RZl9o#.%!endstream +endobj +xref +0 27 +0000000000 65535 f +0000000061 00000 n +0000000102 00000 n +0000000209 00000 n +0000000321 00000 n +0000004077 00000 n +0000004335 00000 n +0000004530 00000 n +0000008451 00000 n +0000008709 00000 n +0000008904 00000 n +0000012557 00000 n +0000012817 00000 n +0000013013 00000 n +0000016826 00000 n +0000017086 00000 n +0000017282 00000 n +0000017352 00000 n +0000017614 00000 n +0000017720 00000 n +0000018072 00000 n +0000018333 00000 n +0000018666 00000 n +0000018924 00000 n +0000019277 00000 n +0000019535 00000 n +0000019871 00000 n +trailer +<< +/ID +[<11a4452bbe31319e89fba7a743537da0><11a4452bbe31319e89fba7a743537da0>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 17 0 R +/Root 16 0 R +/Size 27 +>> +startxref +20134 +%%EOF diff --git a/src/documents/tests/samples/barcodes/split-by-tag-multiple-per-page.pdf b/src/documents/tests/samples/barcodes/split-by-tag-multiple-per-page.pdf new file mode 100644 index 000000000..5c4d97c63 --- /dev/null +++ b/src/documents/tests/samples/barcodes/split-by-tag-multiple-per-page.pdf @@ -0,0 +1,181 @@ +%PDF-1.3 +%東京 ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2 4 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/Contents 15 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +4 0 obj +<< +/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font +>> +endobj +5 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 290 /Length 3461 /Subtype /Image + /Type /XObject /Width 290 +>> +stream +Gb"0M0bW:r$j4o4s3aL9.o/:sRKC1+V[Po_hnP="8Wk>jOsEV^,Y=.E8Wk>jOsEV^,Y=.E8Wk>jOsEV^,Y=.E8Wk>jOsEV^,Y=.EM=P;M`ictLh5:'u?=R'nd;IE]5Hh=BmqB2p^7X$0Q$9UiFPkD[m)hEDD7]3!20S(%m5Eepo,>73Ncpo[qg"0,Gt5J@p\hbEY.UOcVYbgK@oqO7DN/KYR@egT:O\Y"S^Nmne(=m!@M;\.mreAj`lssm2RjQmR*'f[]=0V/jtsN_^"C8&k'PptV(jd(Ymp-?-DiQUlg??aR5p7DE%a+(Q2+a1De[G>Bl&EKZ&,I(pUY]E@qJJG)r-?G9P(rih-1dREuNfk?>O(#o=aSKd[6HOfEV(Z'2t=fFn_3AbT-'E'3sZfj1^M@pYhQ7E1%B!q_i'CLMJZ]APP)MgR*7.Y/pg53RP?TA*/3L-50YH7,u"@RJ5[/9Q6C5NVbVGhM5l%_.?@umb=+S+0N]gQTFE$MV_[_Y246E[o=\bnb0967Q$FISai'U8mksuCAo?M*bkl?R-I0h_YM$B?F8J^DhM5l%EG"?[c+]I2gNP.=5$X;.1Gdp(p8uQo^/LHoiL3GZR2\raAeSG3ICLU;>is%i\_.+PGos32"IH[hA8X4IM[5E1-IZRS7[g)c,U.'3s[J\0_kok/NUqf`[Xe+0N]gQauWsDDo=BhM5l%_.@LHR@?oiRJ5[/9Q6C=:Zc7&>ipIE-50YH`fmsd"IFD+`\u,TrPk$]NL;edD'YO[I2buE1hPl,[ZP+_p2)p[e!QQPfLD$lgUH]`:1Im2@iJ!ODVrHt3K9FeNGTr/\U>Dmjtp]41q&NWk4WXSRF@Oke(@-QRG54@A56WH:1G57Ao?MGP<"Vj3K7l$RCR_b:ZaKGk$5CC.+u(L[aFc^qb6b_]O]p>fgaTjmPE\no9+M@B,b.F]?bTVcV*tKS8EA]mlo3K5;1^!EOO9f^ACUurOc[u`n=fDB;AhhFPj09^)KAJ38&9VV?L8MpH&M<8.ldJV05RX^_no.Ld9qHZg`b02a-m$D"%2.\6nf;,`[G2:]5WQ\V2c@4Gh=&YtOF%n^mA_13^REE`2l0OaBG;Wq]1Y8G/?Zt8UPc;l3PKnX1F]VM=136/NqdnAb9ps/J2L9Q0S)iGGeB)@_DF)%_Cm',a;^\2o]*8-oZUsS%9V$PXmM>H\bU0m00m3&T\6I=`1RmI^`mi+Cibh&sc>8Yj)cJ,VM7Wri3jVEGD+pLJ-LMZAlc^]d[kW$rRCHJJs-pTpHZ/#)PI^.2/_/Gu9ldRQT$2WWCT5#pBp+rKo47:$?VC&L8X%rrR4!(5rE?5)8Xe^PcTIWmmak?b:!t:GHfiH*GJBI/CQ^$TfeZFd^AG<;?^!=gc(929pYE$LqO43ODYD;<\aOu!e^l'@EjKDMb^K5$WP0]nZLJ^%HY#u61\3[enc_V2NHb$M)gp)%RGYQ;01^D,]VFZHi02I1r6C:L6.0i7*Bj-$T6+]-GAcILP+EW]kd`YIUbagAF!G%Ro\=[]cb7.BSXK;E)u5)]kJfT0mL;AEbfoP2a;6*b2r;r'Dt$>2Aq&o4^*)[NnW'2fK24Nao/eo%"\I%"GP'Z0I+"FNhmnk&i,4+8D*45U9lOt5(Y:H%gNYJ4S)E#I0RZO*cDrSi.cAfFP.AeSDgqSi-Obr20;bpKqYoS`%'Rr(9URn[j=kSMi,2qrR42k/aZcu)c8-TRTajWn-2h:0V>:?H.K8QTXcol?4Z\QM\UQ.esGSE+3uQBQEeG#L%A3LQAu,[ID*eB:EYk%6VF=)'\eEfuWs=\dD1g.f8NjCE.oPBA92cbB:e#8i1-tFRQ=g8G;/Vi_h'@1H2o>ZY116t>@KpYJMpA7)Ji/leW#F/+)#V*VC?f+jW%d?qJl]slE4fpD#^99j27h!!U!Boq])FiC1L1hLXTfG0bKqW+XE:1[0q:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9@iJ\8WL&g=b[jOE:;4>9j"6dh3D"AU^/LHoRCY]P]2P,]<+kV\Q$K"$)s"^pPrVEYk.Xc^pR/TYm^lDcP>l2_4-b)`W>jp44-_ftFlpD:RJ3,\612?`R?LT_mQ6\ZT;`dj^,qT?8Tj10;jmBJ\j>br;jihKBC7jHH(V&TjM!^@3D"AU^/LHoRCY]P]2P,]<+kV\Q$K"$)s"`VbpKshhJ;heIJ#,$L#d]nmrG`@m^r4^I;<3g8o>f_?gbP]CkDQP]k60U=20o&8FDiA/iT9X^3d':\+\@Uj;*pUjhAp_-FiO$C\FlYoddS,jF4Z.EjH)?]D%bBCL@$4DBZPtm^q7jK)=uLB&DH`u,3p=6A-(6\RV^<=bJ\F89ip8rc9/%LApI_"ofZO-'3pR6MG?iVY"m@=Eq[a)Y.q"N1qoK.Z\e#:l3*)"BA[ObqR\dSisd?'T6oD_R;-aleNSse-CL'A2.`f0WDraO2OS)NhURji-Dsc/e(A2o3I+\)VOF#I[81:r8`o)>9poa:.b-_B9dZ9lG;Ws3af/8:1cCb4:>XNcW@"N@mF0]uOu[eh;l6"R9!qH)P=aot>tp`%E[oU'ND1afPBSlqWl_5>q`ONacB7HTe^^)FjdQ)cmKTR7qbD9Vk'+?_^P9A:.ET;&?(LdsY0!m+DK&4Rmo3A$I[=j@CUb=RP3b9\eX>=VRf")l#,`aD:3C^AGI]'8L:b8NahC\ZSbZQoafjZ@E([G)<**^]QYZ/-\/Us$loWbJRG[+pr#4u-V^2.7F`lhj\L&UoOsEV^,Y=.E8Wk>jOsEV^,Y=.E8Wk>jOsEV^,Y=.E8Wk>jOsEV^,Y=.E8Wk>j''C?8XL9P~>endstream +endobj +6 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 290 /Length 3802 /Subtype /Image + /Type /XObject /Width 290 +>> +stream +Gb"0M0s\bV$q&G/J(%!dMEdPCi+mj":+lnVm=5//INI)Ff49*4S%J.PEG]YhG"90TqgKU<#1mC0[%"\rkAb?X9m0%=\bggsf*9i;GI3jOn)n\-E(tLG]=Iff*nLVJA9YE8`&:.D17um52nS3pR@'BuYmi#Nq(-`rCL3?aR5kr8:bnZhE]:VmFd\Vb1U.B8oD'q]ZYNm6M4$@;gJBYtcZ1tVk&m)ZR5;)W-1gp`GI)'kQ\h+j'AH>=T?hO:]B47(R$fb8%41WA/9eF9?C-M:3S\hTaNT%`ls#mgH!Qj4iL3_VB"i'SXj#S5Y0?c^9e%nSh_k:3Ao:pVk'a`d'q(KYRXH\B2m4b'5$X;/bfk:U^6P+Uf9LV'Y";&ok.>6_b08,rb.J8:U`qVd?*eH\k2pethM5lEOdZ$Jfs`_Xo=?^G__nF7b^A%/>FE$I?((HGRT*g0^2*GEbhTk6bkl?R._*^*2DS2[>]!0Q26#Lh]@t>"V/Wgs&_Eh1bNrsg1R5i-`^F'q$8/ZcNQb?/&$ab&l]9,+WK$1e;f;-9Kq6%=KRI_oab&l]9,+WK$1gQ`&9B[J%dp!1M:0/cGVUr^Y&/&R@-&K1NOnOOnltfDpHSNR@'BuZ&Qs\p3^pnB$S?=S;D)nI^/(*1Ga)!B')d',P-gVhjg+&1GL[u@N1_Bm.oefbaaNX3>:/gr*mu2B4g:"bflL-7ckM6^,[u*B4>Ju`&9Bcf^m@UR5kp:F-LSfP;;UFZP/nmh8e@Go9=MJSt5(-mlp0RT;Tmpna&[,H1u=QrB+ZJM.\1scb#7Mn)l.k:-:VcH/B.5*DOc%9uNl+135C(NGD#t1NO4LWKbW^c!TMbdDX8a6sM2f1O&HB\99\`1H,o49$,<5r&(Vt:!CgU`2-?eF#ST.CI]$oEt`PmPg%q-p$8So50Gf:pTDB`o>IdeE>qlek2kH"\9ab!<@2U9$J2rG]*_Wc'>)E*D,B2[Q[;fml?Qi?RCE\U>k<").U5,4lm`[X6%s)\mTkOIkDuX%_:Q's;hmG879P-[(c3gRuD@0DDWdeap_^13"74\E;o0\@(5+gZOeMCiI)k!\8S'PaAEV7PDfLBI&mcO,oI9\pV-FT&)MS&\3Frsf3S;D)nVW)JWTA@Q<1M:/9RAm]ccY)ulouriV7V4.Wm-T[ZQu8]D6ht+'6dYM/?e#ibgrPh8e&%B4;gJWOSGDBD58Lk*jNL;.^ci]A-QNk'`Js\0a"UPhCf^/pC@,Gork5Y0"m.A[\;FbFMXNhFHgfCGKs$R;FA@EbCZ,pJJN]R`sPaAfJn)gf`b$I?^nE-8Tk1fnT&MG?28"+/LKjaikc:[\:@WSQ0Ra8*PnH135sRD(+jnB9et\;7bbUbhQ'-)p5eJ=lndoPchMC1O#):L@Qs@gLR;Y3f#4pO$#Xc20A'ccXM8m8&-(Hre).q__X)b0@*V:OMnagLR;Y3f#4pO$#Xc20A'ccXM8m8&-(Hre).q__X)b0@*V:OMnagLR;Y3f#4pO$#Xc!(Lam41+kr*hH\<Shj;F!q+f>DSH"O1;;qq<0`"22O'^"ri;"H3AXY]i4]Z^k.CXCj*T'F<=-0R6b3$\^WQ>C1K.9Tmb:QU^AG)h^?<^>R?[Y^;Wd)d.\>tgaipnG-K6rIU\<*[bZ+I5Ca1b-3>Il`\N)Ir4ql\lb^<1)AQE]o9XW3b2DR(\;fS4jRkn"11U2q`bi_r1B'&1!<;T@*,;\pO'pi(63A]2L'ALUPHqqXlY\ujlXcdqf"VpE1(!K.&do)^!E<0'A'RBbNrt28Yrr_IK$mI\=OF?AlfpD.`G]^oB4B:orR(,]@p(Z.IMMS5AqmOB*FXTeV95ZES(*cGr'G'/%Eg+2O%0u]AD_sRu-H6_.@etTqkTAofdl^9O,mN0!hSf-'dJmp\FDmid+XER9aYXW>'Qic_!-0f<^(PltSmZV7>i>rk+Z/Se\EPgf`b$b^$bCqb5e^`3[V2RIbHepR/OFM.`*C1[!jTmk\qk'@/-eB?n\3hIkg`-D"4TcCC6E][+MJ9K]B2S2i6hH(TptR!;ZB3HJLZo0*hs0_)5bF6:,?k'\Ro@H(GOk0/+]bkk)h_Sie'c'e0DRJ3,Z"m%9odCu(bY'2\4p<1.m[D?G"]NO3>2j8$lgq-f1>is8"'s;a:\b=4[bI,/tcFd<=H8h%'^YHG+)dMOLRh`)M1V*5&^!!h"A^7qkiHdGHCVSZ:>T6r1baT?MG;Hf'bIu*,_.Cp)=lFZcPA@qg]3H:[k00;0Y'2\4p<1.m[D?G"]NO3>2q,]'Pg">YiEL/`q0^cD#5>99UA?`e$VKRQ=]ZQt]<7"Uf>K6hREoMOD/esT-E:Dd\"I7qT67QX^$D,cIOSW0->m.mkKFrd'7J+eqg&o70@t:-Njsq[k+-q6M49jt3HI^G6soQ2^>?fQbOqC9,As6ZH"UK&io]?KcJ0!jdFZ%;Y;^ImE]n('Ln!UDhV$MM\9YWV1O$c3oJQ+(lV60I>gJg"i[4MjGP:\VDI0L/bT1[:IEakNH4r4jf5p)7\;@5cWiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!WiE)!Wf$BiN/nET~>endstream +endobj +7 0 obj +<< +/Contents 16 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.1ec20b3a96e40a35266a0a8a0d634adb 5 0 R /FormXob.bbe0a4b6628a0e8125ab26a62825893b 6 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +8 0 obj +<< +/Contents 17 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +9 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 290 /Length 3500 /Subtype /Image + /Type /XObject /Width 290 +>> +stream +Gb"0M0s\bV$q&G/J($uOju)%+%1&)hn*)-NT`"8nzzzzzzzzzzzz!!'grT65NupmP_`]63jRce!oT8TqIFGMi(@D>9Q18%WpBeutHu8YIA4O7SpKc+sL9F0lZs.b3omCWORUeq#Fn]1ff7pJ#G-kItht;A6pmP_`]63jRce!oT8TqIF@N3&*FmnO#.ck97`6:E%D03I(`QWUU&i9D1[aFc>'f5%G8^-ObfLFJ>\r;M/@@JB>r)?I1e@5,du+nSeX'Eoh!BoPLr@VHWJ@\f-`;Z:LY8Kmo_Ad?D#0[5)F,u]k>=.H$p;]q^?Xu6pgRT0&;GI9.]Q(k==7(su_^mKeN$]^*XT/b_iRI^9\/Rk'_VO.X[X!?($+R'u%ohpmP1W1+Tpkqp$[=RJ65/WUOJ"FCk0:>qq.oTiPM!n,14O/tI1k<0>3<$5]2)lT?d&ATH1smHj(k't2X`iUD'W$A9g"p/H/Qm_8!9o-U&'oK;S+h0mmRk"Rt-k]u$5])/Y.baWi8dIY"AkBe/\3oGMcAUk_L);rMA#.X2i!H.gHJ/`tUi5T+.\FGmdDZ"(U:O*o%bOL")-=7^7DrE74n%8HFqc1)_qsI.l2X9/9=XrGe)G9:D2aqfB^S]TJQh-2j3jnnI0b'oU-pqAhRYDp-&E0eZ@h0kOd.U2CjG:$Z9F`64iQ1)?^./R#Qi;;q9^,G95_HAAGGP@N9hjJ)bTkmPnBP&2>q6mNRbVk[p.ML'C@j^(Kp6jTgZ9`&rR;L1/gVQ-1gJBf,9Jj)8R=&5kB4`+*#*k$W[Pk<$n'e`_=)ulnbsWAV8,n1Y\;=[tT6B[\7M6R:p1O1\nJ`cce;3%4W%9Cu];YgRj<\hRi@\^S#q;\'`3BG@'2DFDp_.g3E)3$iGVE:#8>Yn(i8??dQL.gM#W\4"p(2\i4mRD7k)U"b&c3-?#Z=p[5]00Bh9RD7&iiSJV&)h4)':2Vu(;!l(CTPIJrZHZrfS**lhrDLp#UDVjQe.sCQCb]ds]kIE*doS^q;DVjQe.sCQCb]ds]kIE*doS^q;DVjQe.sCQCb]ds]kIE*doS^q;DVjQe.sCQCb]ds]kIE*doS^q;DVjQe.sCQCb]dt()PtR=ZX#Ne^?[k]n7>Yqk"X@5,N$b[n+tfaEOuZ]=tTY?Y5"1hF(X2o%i[0Y4&I/QW`::2c81eHoLr:lT;0:AQJTg:"6Qqhp&n(qT^RT40.j^1..d;(9\b4QZdNohWf>5rbj0%"E=9M)9$`?o2DU%CYHQ'd/bh(O4X[8`a;i@8^*XN&i6/4oS>^0IF"$YVRS;Lg0=0)JU8j3sU!2h<13!]9bY$3.m58[g;k8V]Y5^+\)>H2oUMjp,BG:)qO1+5JhOIYF/#[obb<8HCGKl;^1-_DuEi6Cq2J1g9.'X4-oCLWfGBu>fA*2$m'&-5<5G.=`Vmk,5B&9%+Ymi#No@Ya?H95tRk08_qgJBXiTC$Zs\'m6IhOI,Nj6S(_kfW7]G@i>d]6GuK^GF1_VGb-dpCd3^o5%kcjh#ajEPFrts0m[M:a=eTY?+/Q$@*@YXq:#sL!:q!ThdT+nZPdC66nmtiM>M)I1WbY,IfmOP01+SS@m%\[Q[3Of"^576*(!7h)CHLZU3?^5"\m^4&XAlS&gq!Tkn-ZV5pa>F_*aPG"-*$7)!s@;;.u'G3*prREq=mOkD[UDr,o,2X7_Vq-@@iZY!i\p.aV;G9mdq<$L'BqoS#Q/D7G5&5=2B&?"jH1t1iW7uLWGC>n*R[oSo2j&%8I1k:217u7s3aHUt;K^6R.'Y9Ko@YXqe('1+7"Oc'p,d.jaf?/$.4ML+cQY]SR95;DOlX_E(t>pel7ZRjbNl-1fe?XOG^S03-W:M%[Eu17u7s3aHUt;K^6R.'Y9Ko@YXqe('1+apUs(p,X02DEh7SfseP+,u1V;r+DqE82-sb)nbWE/3Y5NP"]T:Lk9Zk"(]B\*gf?F9qzzzzzzzzzzzz5k,pqe-_`~>endstream +endobj +10 0 obj +<< +/Contents 18 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.1280b7d13f0587f75dbba24117c3e12a 9 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +11 0 obj +<< +/Contents 19 0 R /MediaBox [ 0 0 612 792 ] /Parent 14 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +12 0 obj +<< +/PageMode /UseNone /Pages 14 0 R /Type /Catalog +>> +endobj +13 0 obj +<< +/Author (anonymous) /CreationDate (D:20251216142815+00'00') /Creator (anonymous) /Keywords () /ModDate (D:20251216142815+00'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (unspecified) /Title (untitled) /Trapped /False +>> +endobj +14 0 obj +<< +/Count 5 /Kids [ 3 0 R 7 0 R 8 0 R 10 0 R 11 0 R ] /Type /Pages +>> +endobj +15 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 164 +>> +stream +GarW05mkI_&4Q=V`ET>Mc.&b?;-+rnF+G1',/ca*pUAWf>EIgiipknB9RY?:(LXAhG(PtB're&F\nA'4a%aIp+-h"W@9D(-_!#HM*Z:^`FOHNUN-;cL_m8qodCk0^Akq$V4PUhHkMQ7^Ejj*/Mgoa@anH4$%uMYhOT~>endstream +endobj +16 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 306 +>> +stream +GasbT4\rsL&4$!fMAlpjH?ZXMZl9aE"XK0lYb%&R$>PaP&&cf78Q/KO<^N*hq5!cWA-.^JqQm+$jrVMM!b1Zf5UJAHTA[O5a#Wj7rghI[YR$H&I1;qf=XrnMp^cl'VsjlS<tc"96#@mAfhO_96j,pMI1=[Fq,J31>/c9Cp=j48@sD.G@[NB&N,!B3"QRendstream +endobj +17 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 185 +>> +stream +GarW0:CDb>&4c2endstream +endobj +18 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 244 +>> +stream +GarW4b6l*?&4Q?hMRuh(23Tpmkt^cRET&,M[qV:g6EQRMs2I6!U^T$](X?G+!r#.NZmDs+Qu<6UTF4R17*n#s2&gSmk2Mk.09@0r[k9DgRu9WjKt]k!Ic1n'J.q+%HiE61]07.7.f1^O]tp[&Fn>&8hUMD]+;spqhR>>LX`Bap0=GTNaa\,&7Zqt_h]FP:T4bD(VQ.g\Pm&EGSuJ6sK$-Os9'08g6q!hekXZP?.6B5f2_iendstream +endobj +19 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 164 +>> +stream +GapQh0E=F,0U\H3T\pNYT^QKk?tc>IP,;W#U1^23ihPEM_?CT3!/hd>6k,goQl7B?"*$l7Jjr"7HE)RrVF!h>6AQD_Ah8\RZl9o#.%!/H~>endstream +endobj +xref +0 20 +0000000000 65535 f +0000000061 00000 n +0000000102 00000 n +0000000209 00000 n +0000000404 00000 n +0000000516 00000 n +0000004168 00000 n +0000008161 00000 n +0000008467 00000 n +0000008662 00000 n +0000012353 00000 n +0000012612 00000 n +0000012808 00000 n +0000012878 00000 n +0000013140 00000 n +0000013226 00000 n +0000013481 00000 n +0000013878 00000 n +0000014154 00000 n +0000014489 00000 n +trailer +<< +/ID +[<3a098456c5603f47bfc8fb47cd3d7621><3a098456c5603f47bfc8fb47cd3d7621>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 13 0 R +/Root 12 0 R +/Size 20 +>> +startxref +14744 +%%EOF diff --git a/src/documents/tests/test_api_app_config.py b/src/documents/tests/test_api_app_config.py index 2480e52ac..f2ed902f4 100644 --- a/src/documents/tests/test_api_app_config.py +++ b/src/documents/tests/test_api_app_config.py @@ -67,6 +67,7 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase): "barcode_max_pages": None, "barcode_enable_tag": None, "barcode_tag_mapping": None, + "barcode_tag_split": None, "ai_enabled": False, "llm_embedding_backend": None, "llm_embedding_model": None, diff --git a/src/documents/tests/test_barcodes.py b/src/documents/tests/test_barcodes.py index 0ad98344d..f7d11db66 100644 --- a/src/documents/tests/test_barcodes.py +++ b/src/documents/tests/test_barcodes.py @@ -822,6 +822,35 @@ class TestTagBarcode(DirectoriesMixin, SampleDirMixin, GetReaderPluginMixin, Tes yield reader reader.cleanup() + @override_settings( + CONSUMER_ENABLE_TAG_BARCODE=True, + CONSUMER_TAG_BARCODE_MAPPING={"TAG:(.*)": "\\g<1>"}, + ) + def test_barcode_without_tag_match(self): + """ + GIVEN: + - Barcode that does not match any TAG mapping pattern + - TAG mapping configured for "TAG:" prefix only + WHEN: + - is_tag property is checked on an ASN barcode + THEN: + - Returns False + """ + test_file = self.BARCODE_SAMPLE_DIR / "barcode-39-asn-123.pdf" + with self.get_reader(test_file) as reader: + reader.detect() + + self.assertGreater( + len(reader.barcodes), + 0, + "Should have detected at least one barcode", + ) + asn_barcode = reader.barcodes[0] + self.assertFalse( + asn_barcode.is_tag, + f"ASN barcode '{asn_barcode.value}' should not match TAG: pattern", + ) + @override_settings(CONSUMER_ENABLE_TAG_BARCODE=True) def test_scan_file_without_matching_barcodes(self): """ @@ -928,3 +957,163 @@ class TestTagBarcode(DirectoriesMixin, SampleDirMixin, GetReaderPluginMixin, Tes # expect error to be caught and logged only tags = reader.metadata.tag_ids self.assertEqual(tags, None) + + @override_settings( + CONSUMER_ENABLE_TAG_BARCODE=True, + CONSUMER_TAG_BARCODE_SPLIT=True, + CONSUMER_TAG_BARCODE_MAPPING={"TAG:(.*)": "\\g<1>"}, + ) + def test_split_on_tag_barcodes(self): + """ + GIVEN: + - PDF containing barcodes with TAG: prefix + - Tag barcode splitting is enabled with TAG: mapping + WHEN: + - File is processed + THEN: + - Splits should occur at pages with TAG barcodes + - Tags should NOT be assigned when tag splitting is enabled (they're assigned during re-consumption) + """ + test_file = self.BARCODE_SAMPLE_DIR / "split-by-tag-basic.pdf" + with self.get_reader(test_file) as reader: + reader.detect() + separator_page_numbers = reader.get_separation_pages() + + self.assertDictEqual(separator_page_numbers, {1: True, 3: True}) + + tags = reader.metadata.tag_ids + self.assertIsNone(tags) + + @override_settings( + CONSUMER_ENABLE_TAG_BARCODE=True, + CONSUMER_TAG_BARCODE_SPLIT=False, + CONSUMER_TAG_BARCODE_MAPPING={"TAG:(.*)": "\\g<1>"}, + ) + def test_no_split_when_tag_split_disabled(self): + """ + GIVEN: + - PDF containing TAG barcodes (TAG:invoice, TAG:receipt) + - Tag barcode splitting is disabled + WHEN: + - File is processed + THEN: + - No separation pages are identified + - Tags are still extracted and assigned + """ + test_file = self.BARCODE_SAMPLE_DIR / "split-by-tag-basic.pdf" + with self.get_reader(test_file) as reader: + reader.run() + separator_page_numbers = reader.get_separation_pages() + + self.assertDictEqual(separator_page_numbers, {}) + + tags = reader.metadata.tag_ids + self.assertEqual(len(tags), 2) + + @override_settings( + CONSUMER_ENABLE_BARCODES=True, + CONSUMER_ENABLE_TAG_BARCODE=True, + CONSUMER_TAG_BARCODE_SPLIT=True, + CONSUMER_TAG_BARCODE_MAPPING={"TAG:(.*)": "\\g<1>"}, + CELERY_TASK_ALWAYS_EAGER=True, + OCR_MODE="skip", + ) + def test_consume_barcode_file_tag_split_and_assignment(self): + """ + GIVEN: + - PDF containing TAG barcodes on pages 2 and 4 (TAG:invoice, TAG:receipt) + - Tag barcode splitting is enabled + WHEN: + - File is consumed + THEN: + - PDF is split into 3 documents at barcode pages + - Each split document has the appropriate TAG barcodes extracted and assigned + - Document 1: page 1 (no tags) + - Document 2: pages 2-3 with TAG:invoice + - Document 3: pages 4-5 with TAG:receipt + """ + test_file = self.BARCODE_SAMPLE_DIR / "split-by-tag-basic.pdf" + dst = settings.SCRATCH_DIR / "split-by-tag-basic.pdf" + shutil.copy(test_file, dst) + + with mock.patch("documents.tasks.ProgressManager", DummyProgressManager): + result = tasks.consume_file( + ConsumableDocument( + source=DocumentSource.ConsumeFolder, + original_file=dst, + ), + None, + ) + + self.assertEqual(result, "Barcode splitting complete!") + + documents = Document.objects.all().order_by("id") + self.assertEqual(documents.count(), 3) + + doc1 = documents[0] + self.assertEqual(doc1.tags.count(), 0) + + doc2 = documents[1] + self.assertEqual(doc2.tags.count(), 1) + self.assertEqual(doc2.tags.first().name, "invoice") + + doc3 = documents[2] + self.assertEqual(doc3.tags.count(), 1) + self.assertEqual(doc3.tags.first().name, "receipt") + + @override_settings( + CONSUMER_ENABLE_TAG_BARCODE=True, + CONSUMER_TAG_BARCODE_SPLIT=True, + CONSUMER_TAG_BARCODE_MAPPING={"ASN(.*)": "ASN_\\g<1>", "TAG:(.*)": "\\g<1>"}, + ) + def test_split_by_mixed_asn_tag_backwards_compat(self): + """ + GIVEN: + - PDF with mixed ASN and TAG barcodes + - Mapping that treats ASN barcodes as tags (backwards compatibility) + - ASN12345 on page 1, TAG:personal on page 3, ASN13456 on page 5, TAG:business on page 7 + WHEN: + - File is consumed + THEN: + - Both ASN and TAG barcodes trigger splits + - Split points are at pages 3, 5, and 7 (page 1 never splits) + - 4 separate documents are produced + """ + test_file = self.BARCODE_SAMPLE_DIR / "split-by-tag-mixed-asn.pdf" + + with self.get_reader(test_file) as reader: + reader.detect() + separator_pages = reader.get_separation_pages() + + self.assertDictEqual(separator_pages, {2: True, 4: True, 6: True}) + + document_list = reader.separate_pages(separator_pages) + self.assertEqual(len(document_list), 4) + + @override_settings( + CONSUMER_ENABLE_TAG_BARCODE=True, + CONSUMER_TAG_BARCODE_SPLIT=True, + CONSUMER_TAG_BARCODE_MAPPING={"TAG:(.*)": "\\g<1>"}, + ) + def test_split_by_tag_multiple_per_page(self): + """ + GIVEN: + - PDF with multiple TAG barcodes on same page + - TAG:invoice and TAG:expense on page 2, TAG:receipt on page 4 + WHEN: + - File is processed + THEN: + - Pages with barcodes trigger splits + - Split points at pages 2 and 4 + - 3 separate documents are produced + """ + test_file = self.BARCODE_SAMPLE_DIR / "split-by-tag-multiple-per-page.pdf" + + with self.get_reader(test_file) as reader: + reader.detect() + separator_pages = reader.get_separation_pages() + + self.assertDictEqual(separator_pages, {1: True, 3: True}) + + document_list = reader.separate_pages(separator_pages) + self.assertEqual(len(document_list), 3) diff --git a/src/paperless/config.py b/src/paperless/config.py index edebb232f..65d790ff4 100644 --- a/src/paperless/config.py +++ b/src/paperless/config.py @@ -116,6 +116,7 @@ class BarcodeConfig(BaseConfig): barcode_max_pages: int = dataclasses.field(init=False) barcode_enable_tag: bool = dataclasses.field(init=False) barcode_tag_mapping: dict[str, str] = dataclasses.field(init=False) + barcode_tag_split: bool = dataclasses.field(init=False) def __post_init__(self) -> None: app_config = self._get_config_instance() @@ -153,6 +154,9 @@ class BarcodeConfig(BaseConfig): self.barcode_tag_mapping = ( app_config.barcode_tag_mapping or settings.CONSUMER_TAG_BARCODE_MAPPING ) + self.barcode_tag_split = ( + app_config.barcode_tag_split or settings.CONSUMER_TAG_BARCODE_SPLIT + ) @dataclasses.dataclass diff --git a/src/paperless/migrations/0006_applicationconfiguration_barcode_tag_split.py b/src/paperless/migrations/0006_applicationconfiguration_barcode_tag_split.py new file mode 100644 index 000000000..03b2d14cc --- /dev/null +++ b/src/paperless/migrations/0006_applicationconfiguration_barcode_tag_split.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.7 on 2025-12-15 21:30 + +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + dependencies = [ + ("paperless", "0005_applicationconfiguration_ai_enabled_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="applicationconfiguration", + name="barcode_tag_split", + field=models.BooleanField( + null=True, + verbose_name="Enables splitting on tag barcodes", + ), + ), + ] diff --git a/src/paperless/models.py b/src/paperless/models.py index 0f727972a..725bff683 100644 --- a/src/paperless/models.py +++ b/src/paperless/models.py @@ -279,6 +279,12 @@ class ApplicationConfiguration(AbstractSingletonModel): null=True, ) + # PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT + barcode_tag_split = models.BooleanField( + verbose_name=_("Enables splitting on tag barcodes"), + null=True, + ) + """ AI related settings """ diff --git a/src/paperless/settings.py b/src/paperless/settings.py index 3d93d8e40..88213813a 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -1149,6 +1149,10 @@ CONSUMER_TAG_BARCODE_MAPPING = dict( ), ) +CONSUMER_TAG_BARCODE_SPLIT: Final[bool] = __get_boolean( + "PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT", +) + CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED: Final[bool] = __get_boolean( "PAPERLESS_CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED", )