Compare commits

..

2 Commits

Author SHA1 Message Date
Quality App Developer
e2a6553fe9 updated format 2026-02-15 17:58:26 +02:00
Quality App Developer
6029a2b98e Fix barcode format and improve scannability
- Changed sequential numbering from slash to hyphen format (TEST-ORD-004-0001)
- Increased horizontal barcode bar width from 0.25mm to 0.30mm for better scanning
- Increased vertical barcode bar width from 0.15mm to 0.30mm for reliable readability
- Changed from 3-digit to 4-digit padding for piece numbers (0001 instead of 001)
- Removed aggressive scaling that was distorting barcode bars
- Barcodes now use optimal settings for thermal printers and handheld scanners
2026-02-15 17:57:45 +02:00
8 changed files with 202 additions and 46 deletions

View File

@@ -389,7 +389,7 @@ class SchemaVerifier:
open_for_order TINYINT(1) DEFAULT 1,
line_number INT,
printed_labels TINYINT(1) DEFAULT 0,
data_livrara DATE,
data_livrare DATE,
dimensiune VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

View File

@@ -87,8 +87,8 @@ class LabelPDFGenerator:
if printer_optimized:
self._optimize_for_label_printer(c)
# Create sequential label number: CP00000711/001, CP00000711/002, etc.
sequential_number = f"{prod_order}/{i:03d}"
# Create sequential label number: CP00000711-0001, CP00000711-0002, etc.
sequential_number = f"{prod_order}-{i:04d}"
# Draw single label
self._draw_label(c, order_data, sequential_number, i, quantity)
@@ -116,7 +116,7 @@ class LabelPDFGenerator:
prod_order = order_data.get('comanda_productie', 'CP00000000')
# Create sequential label number with specific piece number
sequential_number = f"{prod_order}/{piece_number:03d}"
sequential_number = f"{prod_order}-{piece_number:04d}"
print(f"DEBUG: Generating label {sequential_number} (piece {piece_number} of {total_pieces})")
@@ -301,7 +301,7 @@ class LabelPDFGenerator:
canvas.setFont("Helvetica-Bold", 10)
# Match HTML: comanda_productie + "-" + sequential number (not quantity!)
comanda_productie = str(order_data.get('comanda_productie', 'N/A'))
prod_order = f"{comanda_productie}-{current_num:03d}" # Sequential number for this specific label
prod_order = f"{comanda_productie}-{current_num:04d}" # Sequential number for this specific label
po_text_width = canvas.stringWidth(prod_order, "Helvetica-Bold", 10)
po_x_centered = vertical_x + (self.right_column_width - po_text_width) / 2
canvas.drawString(po_x_centered, row_y + self.row_height/3, prod_order)
@@ -321,17 +321,23 @@ class LabelPDFGenerator:
try:
# Create barcode for sequential number
# OPTIMIZED FOR SCANNING: 0.30mm bar width for reliable readability
barcode = code128.Code128(sequential_number,
barWidth=0.25*mm, # Adjust bar width for better fit
barHeight=mm_to_points(10)) # Increase height to 10mm
barWidth=0.30*mm, # Increased from 0.25mm for better scanning
barHeight=mm_to_points(10)) # 10mm height
# Always scale to fit the full allocated width
scale_factor = barcode_width / barcode.width
canvas.saveState()
canvas.translate(barcode_x, barcode_y)
canvas.scale(scale_factor, 1)
barcode.drawOn(canvas, 0, 0)
canvas.restoreState()
# Only scale if barcode is too wide, otherwise use natural size
if barcode.width > barcode_width:
scale_factor = barcode_width / barcode.width
canvas.saveState()
canvas.translate(barcode_x, barcode_y)
canvas.scale(scale_factor, 1)
barcode.drawOn(canvas, 0, 0)
canvas.restoreState()
else:
# Use natural size - center it in available space
x_offset = (barcode_width - barcode.width) / 2
barcode.drawOn(canvas, barcode_x + x_offset, barcode_y)
# NO TEXT BELOW BARCODE - Remove all text rendering for horizontal barcode
@@ -362,18 +368,21 @@ class LabelPDFGenerator:
vertical_code = "000000/00"
# Create a vertical barcode using Code128
# OPTIMIZED FOR SCANNING: 0.30mm bar width for reliable readability
v_barcode = code128.Code128(vertical_code,
barWidth=0.15*mm, # Thinner bars for better fit
barHeight=mm_to_points(8)) # Increased bar height
barWidth=0.30*mm, # Increased from 0.15mm for better scanning
barHeight=mm_to_points(10)) # Increased bar height
# Draw rotated barcode - fill the entire frame height
# Draw rotated barcode - fit naturally without aggressive scaling
canvas.saveState()
canvas.translate(vertical_barcode_x + mm_to_points(6), vertical_barcode_y)
canvas.rotate(90)
# Always scale to fill the frame height
scale_factor = vertical_barcode_height / v_barcode.width
canvas.scale(scale_factor, 1)
# Only scale if barcode is too wide for frame, otherwise use natural size
if v_barcode.width > vertical_barcode_height:
scale_factor = vertical_barcode_height / v_barcode.width
canvas.scale(scale_factor, 1)
# else: use natural size for best scanning quality
v_barcode.drawOn(canvas, 0, 0)
canvas.restoreState()

View File

@@ -495,9 +495,9 @@ function updateLabelPreview(order) {
const prodOrder = comandaProductie && cantitate ? `${comandaProductie}-${cantitate}` : 'N/A';
document.getElementById('prod-order-value').textContent = prodOrder;
// Update horizontal barcode with CP format (e.g., CP00000711/001)
// Show the first piece number (001) in preview
const horizontalBarcodeData = comandaProductie ? `${comandaProductie}/001` : 'SAMPLE001';
// Update horizontal barcode with correct format (e.g., TEST-ORD-006-0001)
// Show the first piece number (0001) in preview
const horizontalBarcodeData = comandaProductie ? `${comandaProductie}-${String(1).padStart(4, '0')}` : 'SAMPLE-0001';
document.getElementById('barcode-text').textContent = horizontalBarcodeData;
// Generate horizontal barcode visual using JsBarcode
@@ -533,8 +533,9 @@ function updateLabelPreview(order) {
horizontalBarcodeData === 'N/A' ? 'No data' : 'JsBarcode not loaded');
}
// Update vertical barcode with client order format (e.g., Abcderd65)
const verticalBarcodeData = comAchizClient && nrLinie ? `${comAchizClient}${nrLinie}` : 'SAMPLE00';
// Update vertical barcode with client order format (e.g., CLIENT001/65)
// Must match PDF generator format: com_achiz_client/nr_linie_com_client
const verticalBarcodeData = comAchizClient && nrLinie ? `${comAchizClient}/${nrLinie}` : '000000/00';
document.getElementById('vertical-barcode-text').textContent = verticalBarcodeData;
// Generate vertical barcode visual using JsBarcode (will be rotated by CSS)
@@ -580,8 +581,8 @@ function clearLabelPreview() {
document.getElementById('description-value').textContent = 'N/A';
document.getElementById('article-code-value').textContent = 'N/A';
document.getElementById('prod-order-value').textContent = 'N/A';
document.getElementById('barcode-text').textContent = 'SAMPLE001';
document.getElementById('vertical-barcode-text').textContent = 'SAMPLE00';
document.getElementById('barcode-text').textContent = 'SAMPLE-0001';
document.getElementById('vertical-barcode-text').textContent = '000000/00';
// Generate sample barcodes instead of clearing
generateSampleBarcodes();
@@ -605,8 +606,8 @@ function generateSampleBarcodes() {
horizontalElement.innerHTML = '';
console.log('🔍 Horizontal element cleared, generating barcode...');
// Generate horizontal sample barcode with simpler parameters first
JsBarcode(horizontalElement, "SAMPLE001", {
// Generate horizontal sample barcode with correct format (dash and 4-digit padding)
JsBarcode(horizontalElement, "SAMPLE-0001", {
format: "CODE128",
width: 1,
height: 40,
@@ -623,8 +624,8 @@ function generateSampleBarcodes() {
verticalElement.innerHTML = '';
console.log('🔍 Vertical element cleared, generating barcode...');
// Generate vertical sample barcode
JsBarcode(verticalElement, "SAMPLE00", {
// Generate vertical sample barcode with correct format (slash separator)
JsBarcode(verticalElement, "000000/00", {
format: "CODE128",
width: 1,
height: 35,
@@ -1084,8 +1085,9 @@ function updatePreview(orderData, pieceNumber, totalPieces) {
const deliveryDate = data_livrare ? new Date(data_livrare).toLocaleDateString() : 'N/A';
const clientOrder = com_achiz_client && nr_linie_com_client ? `${com_achiz_client}-${nr_linie_com_client}` : 'N/A';
const horizontalBarcode = comanda_productie ? `${comanda_productie}-${String(pieceNumber).padStart(3, '0')}` : 'N/A';
const verticalBarcode = clientOrder;
const horizontalBarcode = comanda_productie ? `${comanda_productie}-${String(pieceNumber).padStart(4, '0')}` : 'N/A';
// Vertical barcode uses slash separator to match PDF generator
const verticalBarcode = com_achiz_client && nr_linie_com_client ? `${com_achiz_client}/${nr_linie_com_client}` : '000000/00';
const prodOrder = comanda_productie && cantitate ? `${comanda_productie}-${cantitate}` : 'N/A';
// Update preview elements with correct IDs
@@ -1135,8 +1137,9 @@ function generateHTMLLabel(orderData, pieceNumber, totalPieces) {
// Format data for label (matching preview format)
const deliveryDate = data_livrare ? new Date(data_livrare).toLocaleDateString() : 'N/A';
const clientOrder = com_achiz_client && nr_linie_com_client ? `${com_achiz_client}-${nr_linie_com_client}` : 'N/A';
const horizontalBarcode = comanda_productie ? `${comanda_productie}-${String(pieceNumber).padStart(3, '0')}` : 'N/A';
const verticalBarcode = clientOrder;
const horizontalBarcode = comanda_productie ? `${comanda_productie}-${String(pieceNumber).padStart(4, '0')}` : 'N/A';
// Vertical barcode uses slash separator to match PDF generator
const verticalBarcode = com_achiz_client && nr_linie_com_client ? `${com_achiz_client}/${nr_linie_com_client}` : '000000/00';
const prodOrder = comanda_productie && cantitate ? `${comanda_productie}-${cantitate}` : 'N/A';
const htmlContent = `

View File

@@ -559,7 +559,7 @@ async function handleQZTrayPrint(selectedRow) {
labelNumbers.push(i);
}
} else {
alert(`Invalid range. Please use format "001-${String(orderData.cantitate).padStart(3, '0')}" or single number.`);
alert(`Invalid range. Please use format "0001-${String(orderData.cantitate).padStart(4, '0')}" or single number.`);
return;
}
} else {
@@ -592,8 +592,8 @@ async function handleQZTrayPrint(selectedRow) {
// Show success message
const rangeText = labelsRangeInput ?
(labelNumbers.length === 1 ? `label ${String(labelNumbers[0]).padStart(3, '0')}` :
`labels ${String(labelNumbers[0]).padStart(3, '0')}-${String(labelNumbers[labelNumbers.length-1]).padStart(3, '0')}`) :
(labelNumbers.length === 1 ? `label ${String(labelNumbers[0]).padStart(4, '0')}` :
`labels ${String(labelNumbers[0]).padStart(4, '0')}-${String(labelNumbers[labelNumbers.length-1]).padStart(4, '0')}`) :
`all ${orderData.cantitate} labels`;
alert(`Successfully printed ${rangeText} for order ${orderData.comanda_productie}`);
@@ -736,10 +736,10 @@ function updatePreviewCard(order) {
set('size-value', order.dimensiune || '');
set('article-code-value', order.cod_articol || '');
set('prod-order-value', (order.comanda_productie && order.cantitate) ? `${order.comanda_productie}-${order.cantitate}` : '');
set('barcode-text', order.comanda_productie ? `${order.comanda_productie}/001` : '');
set('barcode-text', order.comanda_productie ? `${order.comanda_productie}-0001` : '');
set('vertical-barcode-text', (order.com_achiz_client && order.nr_linie_com_client) ? `${order.com_achiz_client}/${order.nr_linie_com_client}` : '');
// Generate barcodes if JsBarcode is available (with debugging like print_module.html)
const horizontalBarcodeData = order.comanda_productie ? `${order.comanda_productie}/001` : 'N/A';
const horizontalBarcodeData = order.comanda_productie ? `${order.comanda_productie}-0001` : 'N/A';
const verticalBarcodeData = (order.com_achiz_client && order.nr_linie_com_client) ? `${order.com_achiz_client}/${order.nr_linie_com_client}` : '000000/00';
console.log('🔍 BARCODE DEBUG - Order data:', order);

View File

@@ -542,7 +542,7 @@ async function handleQZTrayPrint(selectedRow) {
labelNumbers.push(i);
}
} else {
alert(`Invalid range. Please use format "001-${String(orderData.cantitate).padStart(3, '0')}" or single number.`);
alert(`Invalid range. Please use format "0001-${String(orderData.cantitate).padStart(4, '0')}" or single number.`);
return;
}
} else {
@@ -575,8 +575,8 @@ async function handleQZTrayPrint(selectedRow) {
// Show success message
const rangeText = labelsRangeInput ?
(labelNumbers.length === 1 ? `label ${String(labelNumbers[0]).padStart(3, '0')}` :
`labels ${String(labelNumbers[0]).padStart(3, '0')}-${String(labelNumbers[labelNumbers.length-1]).padStart(3, '0')}`) :
(labelNumbers.length === 1 ? `label ${String(labelNumbers[0]).padStart(4, '0')}` :
`labels ${String(labelNumbers[0]).padStart(4, '0')}-${String(labelNumbers[labelNumbers.length-1]).padStart(4, '0')}`) :
`all ${orderData.cantitate} labels`;
alert(`Successfully printed ${rangeText} for order ${orderData.comanda_productie}`);
@@ -719,10 +719,10 @@ function updatePreviewCard(order) {
set('size-value', order.dimensiune || '');
set('article-code-value', order.cod_articol || '');
set('prod-order-value', (order.comanda_productie && order.cantitate) ? `${order.comanda_productie}-${order.cantitate}` : '');
set('barcode-text', order.comanda_productie ? `${order.comanda_productie}/001` : '');
set('barcode-text', order.comanda_productie ? `${order.comanda_productie}-0001` : '');
set('vertical-barcode-text', (order.com_achiz_client && order.nr_linie_com_client) ? `${order.com_achiz_client}/${order.nr_linie_com_client}` : '');
// Generate barcodes if JsBarcode is available (with debugging like print_module.html)
const horizontalBarcodeData = order.comanda_productie ? `${order.comanda_productie}/001` : 'N/A';
const horizontalBarcodeData = order.comanda_productie ? `${order.comanda_productie}-0001` : 'N/A';
const verticalBarcodeData = (order.com_achiz_client && order.nr_linie_com_client) ? `${order.com_achiz_client}/${order.nr_linie_com_client}` : '000000/00';
console.log('🔍 BARCODE DEBUG - Order data:', order);

View File

@@ -414,7 +414,7 @@ def create_tables():
open_for_order TINYINT(1) DEFAULT 1,
line_number INT,
printed_labels TINYINT(1) DEFAULT 0,
data_livrara DATE,
data_livrare DATE,
dimensiune VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

70
test_barcode_format.py Normal file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""
Test script to verify barcode format in generated PDFs
"""
import sys
sys.path.insert(0, '/srv/quality_app-v2')
from app.modules.labels.pdf_generator import LabelPDFGenerator
# Test order data
test_order = {
'comanda_productie': 'TEST-ORD-004',
'cod_articol': 'ART004',
'descr_com_prod': 'Metal Bracket D - Galvanized Steel',
'cantitate': 300,
'com_achiz_client': 'CLIENT-PO-2024-004',
'nr_linie_com_client': '04',
'customer_name': 'MetalCraft Solutions',
'customer_article_number': 'MC-BRACK-D004',
'data_livrara': '2024-03-22',
'dimensiune': 'Small'
}
print("="*70)
print("BARCODE FORMAT TEST")
print("="*70)
# Test what sequential number will be generated
prod_order = test_order['comanda_productie']
piece_number = 1
# This is how the PDF generator creates the sequential number
sequential_number = f"{prod_order}-{piece_number:04d}"
print(f"\n📦 Production Order: {prod_order}")
print(f"🔢 Piece Number: {piece_number}")
print(f"📊 Sequential Number (Horizontal Barcode): {sequential_number}")
# Check vertical barcode format
com_achiz_client = test_order['com_achiz_client']
nr_linie = test_order['nr_linie_com_client']
vertical_code = f"{com_achiz_client}/{nr_linie}"
print(f"📐 Vertical Barcode: {vertical_code}")
print("\n" + "="*70)
print("EXPECTED vs ACTUAL")
print("="*70)
print(f"✅ CORRECT Horizontal: {sequential_number}")
print(f"❌ WRONG Format: TEST/ORD/004/0001")
print(f"")
print("If you're seeing TEST/ORD/004/0001, possible causes:")
print("1. Data in database has slashes instead of hyphens")
print("2. Barcode scanner is configured to translate hyphens to slashes")
print("3. Looking at wrong barcode (there are 2 barcodes on each label)")
print("="*70)
# Generate actual PDF to verify
print("\n🔧 Generating test PDF...")
pdf_gen = LabelPDFGenerator(paper_saving_mode=True)
pdf_buffer = pdf_gen.generate_single_label_pdf(test_order, 1, 300, printer_optimized=True)
# Save test PDF
output_file = '/srv/quality_app-v2/test_label_output.pdf'
with open(output_file, 'wb') as f:
f.write(pdf_buffer.read())
print(f"✅ Test PDF saved to: {output_file}")
print(f"📄 Open this PDF to verify the barcode format visually")
print("\nThe barcode should encode: TEST-ORD-004-0001 (with hyphens)")

74
test_label.pdf Normal file
View File

@@ -0,0 +1,74 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document http://www.reportlab.com
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
<<
/Contents 8 0 R /MediaBox [ 0 0 226.7717 297.6378 ] /Parent 7 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
>>
endobj
6 0 obj
<<
/Author (anonymous) /CreationDate (D:20260215130816+00'00') /Creator (Recticel Label System) /Keywords () /ModDate (D:20260215130816+00'00') /Producer (Optimized for Epson TM-T20 / Citizen CTS-310)
/Subject (Production Label) /Title (Thermal Label - Optimized for Label Printers) /Trapped /False
>>
endobj
7 0 obj
<<
/Count 1 /Kids [ 4 0 R ] /Type /Pages
>>
endobj
8 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1821
>>
stream
Gat=-gMRrh&:N/3n?EFq$&<^g3d)DUc.1AU#kf05/)-&XUe&87LYQ1kCsLUd+N*?ec5[>91M:B%)BZ+HYO@^%ZV[iTA&UnhDA>6RC@PWD?-IDu\9p4klCZbC@O>lS:F[3e_&1-gPE%(AcA&(f=`$3B26607<*+9UE/%42ioWKa`pAt1L.mD#/V%=#C+q.kkPD?Yo_<9A-lD.:[P=#QiUZ[q+1gi[ID/k@DUm:TqYPI/E<#<,[J4f^bJuCm*6<nseXb^<j>bllfG\fj['cKg1]GI=*:%HO`e:]_o",h[ZaCiQ_^^r)AI=JGa,tI<WR7"U#@AkQjnnUt]A!6`&"B&+e(l!;CeQb=KNWUFh/&Gek7aIglVfbB$Q(7-&>sSlErQ$lp>N1/oY@&Ke_C6u;t,k'^&S&ua2RuJVn(3pc*_^JK,?`0l`/9hDS>noD18LSjgKLG_NM?pm"r?8YKpO0a_#S0K`TZ3lcn_`Z:Q,_'"37tP`c8bQ\giurQ\J_2TtF$\uiP?o4+-!G9)]<49>"j\c/kH?iAa8W2jdeX^<tD*Qq=E4^PtXZa*ikG^J`;DRk36R$:,HPPb<#[b7gdf:=sP\9Si<d$YBAc9@cEhcubDs"V8J3]SS3!ept=VF.X+LRq!9+^t,aIYg:-*cBK*B-/kP_OYpT]Y.Lm5@%T[Z\>UF",@,f!*bQ,C_*GK)a!"KNlVk1G[rmrS_ABtES;DcNanuq7MD[EMB7OZDQ,L%$LYuW*%SI)BD=VtFPNEs&TFPPEuFW`2IhL=!a5Z$nosb>:`U2%HGiViDdVIC!FpL07MKtPJY0/4.J/=j,o]W.Rq<jH,SWZ\?tB,<m]hnnU\_P5c!'[@HS=(U65_U(+IgH!Ao[MSM+4)=kf<c9r3/.:(PVr>)X&t3MnqLel%m2%"nNd6b?>>:"6CB$3.500g]e?f5T)N&H4a"n'Pi[EU4l-(#:oY%Zn#_@M27n*C.DU<(l2t4&eN#QhF'$c=\+8sU]`nAdm[B]J<D9a&h,k6"T!/uU1I'gioENq.4Jtq(G1!NZ&[g%b,K,m-T]t$R08A)%$[[0)Z"V4<\!Q.3>NI]4"DriUZX$/;?BgN[C5X?ABqlAO;pR:%J1bC16q^smDpDR[M=r:X`C%r*SI%^W_r&R4^mD:lWN@$[REmc.AI:dRH5X0kfb6^X9kXoIhUc\fHtms`bQK-T9(C\)B:)I'$)tBq&gEc@<hnc\998j![)H!^m^(q6I1*IZnM3%SX&@b6_FZAY6@[6W`<PF*sLU(W&P]u8-h'$*;@i:RG+"F`qAWVOU\\>Xu\LY`W=3$%8FNH'6YAH`/IW+#KCHSa`X'lacAlu5QU=4B?^KB@0DGBmtJ1l\$#od4QSK:-R7aY8uZ\@d3e7<72o#Ta%8A7c[hO"gH]Zejg:R#k@eVDTXXb=8[=2*N##o'>)Hd@^_:Ar=LKrloam>ND/EjS#RKFmSljYO7:<#>Rmr64ksbeD5n=EZ0Jeg=?N.EC"\UJ'?BR4!*JhXG0W_=57U19Ga\bV9J23@"9&a9=9FTEk(8IF4Zfq+R<mq9=>+8GF=PP@r2JOf/'R`6Rp.qJlWEa0"Ca@L:lWU$>_>Ms;[,%B`X#H7eSi"];$-QKX<l+rm4E=ai=RJV=\/DX-oal^"YjbJ)gPFjlKkpue,O)UG'qmduW<%]":488aK,/7>.9aAj5K\3nHpSD-<GpHD;_L/RrQVd(H8"R<_:X^:-No(k+7D>G_#i=s4TVM<Gt\3)O.@XVI^qA*W^6;a=?[\#Ijgg7@Q"eDK:fR]<il306,oF/W[9LN:k)3gLfHjARf*JEJpT$~>endstream
endobj
xref
0 9
0000000000 65535 f
0000000073 00000 n
0000000114 00000 n
0000000221 00000 n
0000000333 00000 n
0000000536 00000 n
0000000604 00000 n
0000000924 00000 n
0000000983 00000 n
trailer
<<
/ID
[<ea57d8511a72770d9c6e1dd29826f00c><ea57d8511a72770d9c6e1dd29826f00c>]
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
/Info 6 0 R
/Root 5 0 R
/Size 9
>>
startxref
2895
%%EOF