1use printpdf::*;
6use serde::{Deserialize, Serialize};
7use std::io::BufWriter;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct DanfeInput {
12 pub chave_acesso: String,
13 pub numero: u32,
14 pub serie: u16,
15 pub data_emissao: String,
16 pub natureza_operacao: String,
17 pub protocolo: Option<String>,
18 pub data_autorizacao: Option<String>,
19 pub emitente: DanfeEmitente,
20 pub destinatario: Option<DanfeDestinatario>,
21 pub itens: Vec<DanfeItem>,
22 pub totais: DanfeTotais,
23 pub transporte: Option<DanfeTransporte>,
24 pub informacoes_complementares: Option<String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct DanfeEmitente {
29 pub cnpj: String,
30 pub razao_social: String,
31 pub nome_fantasia: Option<String>,
32 pub inscricao_estadual: Option<String>,
33 pub endereco: String,
34 pub municipio: String,
35 pub uf: String,
36 pub cep: String,
37 pub telefone: Option<String>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct DanfeDestinatario {
42 pub cnpj_cpf: String,
43 pub razao_social: String,
44 pub inscricao_estadual: Option<String>,
45 pub endereco: String,
46 pub municipio: String,
47 pub uf: String,
48 pub cep: String,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct DanfeItem {
53 pub numero: u32,
54 pub codigo: String,
55 pub descricao: String,
56 pub ncm: String,
57 pub cfop: String,
58 pub unidade: String,
59 pub quantidade: f64,
60 pub valor_unitario: f64,
61 pub valor_total: f64,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct DanfeTotais {
66 pub base_calculo_icms: f64,
67 pub valor_icms: f64,
68 pub base_calculo_st: f64,
69 pub valor_st: f64,
70 pub valor_produtos: f64,
71 pub valor_frete: f64,
72 pub valor_seguro: f64,
73 pub valor_desconto: f64,
74 pub valor_ipi: f64,
75 pub valor_total: f64,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct DanfeTransporte {
80 pub modalidade: String,
81 pub transportadora: Option<String>,
82 pub placa: Option<String>,
83 pub uf: Option<String>,
84}
85
86pub fn gerar_danfe(input: &DanfeInput) -> Result<Vec<u8>, String> {
88 let (doc, page1, layer1) = PdfDocument::new(
90 "DANFE - Documento Auxiliar da Nota Fiscal Eletrônica",
91 Mm(210.0),
92 Mm(297.0),
93 "Página 1",
94 );
95
96 let current_layer = doc.get_page(page1).get_layer(layer1);
97
98 let font = doc.add_builtin_font(BuiltinFont::Helvetica)
100 .map_err(|e| format!("Erro ao carregar fonte: {:?}", e))?;
101 let font_bold = doc.add_builtin_font(BuiltinFont::HelveticaBold)
102 .map_err(|e| format!("Erro ao carregar fonte bold: {:?}", e))?;
103
104 let mut y: f32 = 280.0; y = draw_header(¤t_layer, &font, &font_bold, input, y)?;
108
109 y = draw_emitente(¤t_layer, &font, &font_bold, &input.emitente, y)?;
111
112 if let Some(ref dest) = input.destinatario {
114 y = draw_destinatario(¤t_layer, &font, &font_bold, dest, y)?;
115 }
116
117 y = draw_itens(¤t_layer, &font, &font_bold, &input.itens, y)?;
119
120 y = draw_totais(¤t_layer, &font, &font_bold, &input.totais, y)?;
122
123 if let Some(ref info) = input.informacoes_complementares {
125 let _ = draw_info_complementares(¤t_layer, &font, &font_bold, info, y)?;
126 }
127
128 draw_footer(¤t_layer, &font, input)?;
130
131 let mut buffer = Vec::new();
133 {
134 let mut writer = BufWriter::new(&mut buffer);
135 doc.save(&mut writer)
136 .map_err(|e| format!("Erro ao salvar PDF: {:?}", e))?;
137 }
138
139 Ok(buffer)
140}
141
142fn draw_header(
143 layer: &PdfLayerReference,
144 font: &IndirectFontRef,
145 font_bold: &IndirectFontRef,
146 input: &DanfeInput,
147 mut y: f32,
148) -> Result<f32, String> {
149 layer.use_text("DANFE", 18.0, Mm(90.0), Mm(y), font_bold);
151 y -= 6.0;
152
153 layer.use_text(
154 "Documento Auxiliar da Nota Fiscal Eletrônica",
155 8.0,
156 Mm(65.0),
157 Mm(y),
158 font,
159 );
160 y -= 8.0;
161
162 draw_box(layer, 10.0, y - 30.0, 190.0, 35.0);
164
165 let nfe_info = format!("NF-e Nº {:09} | Série {:03}", input.numero, input.serie);
167 layer.use_text(&nfe_info, 12.0, Mm(15.0), Mm(y - 5.0), font_bold);
168 y -= 10.0;
169
170 layer.use_text(
172 &format!("Emissão: {}", input.data_emissao),
173 10.0,
174 Mm(15.0),
175 Mm(y - 5.0),
176 font,
177 );
178
179 layer.use_text(
181 &format!("Natureza: {}", input.natureza_operacao),
182 10.0,
183 Mm(100.0),
184 Mm(y - 5.0),
185 font,
186 );
187 y -= 10.0;
188
189 layer.use_text("CHAVE DE ACESSO", 8.0, Mm(15.0), Mm(y - 5.0), font_bold);
191 y -= 5.0;
192 layer.use_text(&format_chave(&input.chave_acesso), 9.0, Mm(15.0), Mm(y - 5.0), font);
193 y -= 10.0;
194
195 if let Some(ref prot) = input.protocolo {
197 layer.use_text(
198 &format!(
199 "Protocolo: {} - {}",
200 prot,
201 input.data_autorizacao.as_deref().unwrap_or("")
202 ),
203 8.0,
204 Mm(15.0),
205 Mm(y - 5.0),
206 font,
207 );
208 }
209 y -= 10.0;
210
211 Ok(y)
212}
213
214fn draw_emitente(
215 layer: &PdfLayerReference,
216 font: &IndirectFontRef,
217 font_bold: &IndirectFontRef,
218 emit: &DanfeEmitente,
219 mut y: f32,
220) -> Result<f32, String> {
221 draw_section_title(layer, font_bold, "EMITENTE", y);
223 y -= 5.0;
224
225 draw_box(layer, 10.0, y - 30.0, 190.0, 35.0);
227
228 layer.use_text(&emit.razao_social, 10.0, Mm(15.0), Mm(y - 5.0), font_bold);
230
231 layer.use_text(
233 &format!("CNPJ: {}", format_cnpj(&emit.cnpj)),
234 9.0,
235 Mm(140.0),
236 Mm(y - 5.0),
237 font,
238 );
239 y -= 8.0;
240
241 layer.use_text(&emit.endereco, 9.0, Mm(15.0), Mm(y - 5.0), font);
243 y -= 6.0;
244
245 layer.use_text(
247 &format!("{} - {} - CEP: {}", emit.municipio, emit.uf, emit.cep),
248 9.0,
249 Mm(15.0),
250 Mm(y - 5.0),
251 font,
252 );
253
254 layer.use_text(
256 &format!("IE: {}", emit.inscricao_estadual.as_deref().unwrap_or("-")),
257 9.0,
258 Mm(140.0),
259 Mm(y - 5.0),
260 font,
261 );
262 y -= 6.0;
263
264 if let Some(ref tel) = emit.telefone {
266 layer.use_text(&format!("Fone: {}", tel), 9.0, Mm(15.0), Mm(y - 5.0), font);
267 }
268 y -= 10.0;
269
270 Ok(y)
271}
272
273fn draw_destinatario(
274 layer: &PdfLayerReference,
275 font: &IndirectFontRef,
276 font_bold: &IndirectFontRef,
277 dest: &DanfeDestinatario,
278 mut y: f32,
279) -> Result<f32, String> {
280 draw_section_title(layer, font_bold, "DESTINATÁRIO / REMETENTE", y);
281 y -= 5.0;
282
283 draw_box(layer, 10.0, y - 25.0, 190.0, 30.0);
284
285 layer.use_text(&dest.razao_social, 10.0, Mm(15.0), Mm(y - 5.0), font_bold);
287
288 layer.use_text(
290 &format!("CNPJ/CPF: {}", format_cnpj_cpf(&dest.cnpj_cpf)),
291 9.0,
292 Mm(140.0),
293 Mm(y - 5.0),
294 font,
295 );
296 y -= 8.0;
297
298 layer.use_text(&dest.endereco, 9.0, Mm(15.0), Mm(y - 5.0), font);
300 y -= 6.0;
301
302 layer.use_text(
304 &format!("{} - {} - CEP: {}", dest.municipio, dest.uf, dest.cep),
305 9.0,
306 Mm(15.0),
307 Mm(y - 5.0),
308 font,
309 );
310
311 layer.use_text(
313 &format!("IE: {}", dest.inscricao_estadual.as_deref().unwrap_or("-")),
314 9.0,
315 Mm(140.0),
316 Mm(y - 5.0),
317 font,
318 );
319 y -= 15.0;
320
321 Ok(y)
322}
323
324fn draw_itens(
325 layer: &PdfLayerReference,
326 font: &IndirectFontRef,
327 font_bold: &IndirectFontRef,
328 itens: &[DanfeItem],
329 mut y: f32,
330) -> Result<f32, String> {
331 draw_section_title(layer, font_bold, "PRODUTOS / SERVIÇOS", y);
332 y -= 5.0;
333
334 let headers = ["Cód.", "Descrição", "NCM", "CFOP", "Un", "Qtd", "V.Unit", "V.Total"];
336 let x_positions: [f32; 8] = [15.0, 35.0, 100.0, 125.0, 145.0, 160.0, 175.0, 190.0];
337
338 for (i, header) in headers.iter().enumerate() {
339 layer.use_text(*header, 7.0, Mm(x_positions[i]), Mm(y - 3.0), font_bold);
340 }
341 y -= 5.0;
342
343 draw_line(layer, 10.0, y, 200.0, y);
345 y -= 3.0;
346
347 for item in itens.iter().take(20) {
349 layer.use_text(&item.codigo, 7.0, Mm(15.0), Mm(y), font);
350
351 let desc = if item.descricao.len() > 40 {
353 format!("{}...", &item.descricao[..37])
354 } else {
355 item.descricao.clone()
356 };
357 layer.use_text(&desc, 7.0, Mm(35.0), Mm(y), font);
358
359 layer.use_text(&item.ncm, 7.0, Mm(100.0), Mm(y), font);
360 layer.use_text(&item.cfop, 7.0, Mm(125.0), Mm(y), font);
361 layer.use_text(&item.unidade, 7.0, Mm(145.0), Mm(y), font);
362 layer.use_text(&format!("{:.2}", item.quantidade), 7.0, Mm(160.0), Mm(y), font);
363 layer.use_text(&format!("{:.2}", item.valor_unitario), 7.0, Mm(175.0), Mm(y), font);
364 layer.use_text(&format!("{:.2}", item.valor_total), 7.0, Mm(190.0), Mm(y), font);
365
366 y -= 5.0;
367 }
368
369 y -= 5.0;
370 Ok(y)
371}
372
373fn draw_totais(
374 layer: &PdfLayerReference,
375 font: &IndirectFontRef,
376 font_bold: &IndirectFontRef,
377 totais: &DanfeTotais,
378 mut y: f32,
379) -> Result<f32, String> {
380 draw_section_title(layer, font_bold, "CÁLCULO DO IMPOSTO", y);
381 y -= 5.0;
382
383 draw_box(layer, 10.0, y - 25.0, 190.0, 30.0);
384
385 layer.use_text(
387 &format!("Base ICMS: R$ {:.2}", totais.base_calculo_icms),
388 8.0,
389 Mm(15.0),
390 Mm(y - 5.0),
391 font,
392 );
393 layer.use_text(
394 &format!("Valor ICMS: R$ {:.2}", totais.valor_icms),
395 8.0,
396 Mm(60.0),
397 Mm(y - 5.0),
398 font,
399 );
400 layer.use_text(
401 &format!("Base ST: R$ {:.2}", totais.base_calculo_st),
402 8.0,
403 Mm(110.0),
404 Mm(y - 5.0),
405 font,
406 );
407 layer.use_text(
408 &format!("Valor ST: R$ {:.2}", totais.valor_st),
409 8.0,
410 Mm(155.0),
411 Mm(y - 5.0),
412 font,
413 );
414 y -= 8.0;
415
416 layer.use_text(
418 &format!("V. Produtos: R$ {:.2}", totais.valor_produtos),
419 8.0,
420 Mm(15.0),
421 Mm(y - 5.0),
422 font,
423 );
424 layer.use_text(
425 &format!("V. Frete: R$ {:.2}", totais.valor_frete),
426 8.0,
427 Mm(60.0),
428 Mm(y - 5.0),
429 font,
430 );
431 layer.use_text(
432 &format!("V. Desconto: R$ {:.2}", totais.valor_desconto),
433 8.0,
434 Mm(110.0),
435 Mm(y - 5.0),
436 font,
437 );
438 layer.use_text(
439 &format!("V. IPI: R$ {:.2}", totais.valor_ipi),
440 8.0,
441 Mm(155.0),
442 Mm(y - 5.0),
443 font,
444 );
445 y -= 10.0;
446
447 layer.use_text(
449 &format!("VALOR TOTAL DA NOTA: R$ {:.2}", totais.valor_total),
450 12.0,
451 Mm(120.0),
452 Mm(y - 5.0),
453 font_bold,
454 );
455 y -= 15.0;
456
457 Ok(y)
458}
459
460fn draw_info_complementares(
461 layer: &PdfLayerReference,
462 font: &IndirectFontRef,
463 font_bold: &IndirectFontRef,
464 info: &str,
465 mut y: f32,
466) -> Result<f32, String> {
467 draw_section_title(layer, font_bold, "INFORMAÇÕES COMPLEMENTARES", y);
468 y -= 5.0;
469
470 draw_box(layer, 10.0, y - 20.0, 190.0, 25.0);
471
472 let max_chars = 100;
474 let mut current_y = y - 5.0;
475 for chunk in info.chars().collect::<Vec<_>>().chunks(max_chars) {
476 let line: String = chunk.iter().collect();
477 layer.use_text(&line, 7.0, Mm(15.0), Mm(current_y), font);
478 current_y -= 4.0;
479 }
480
481 y -= 25.0;
482 Ok(y)
483}
484
485fn draw_footer(
486 layer: &PdfLayerReference,
487 font: &IndirectFontRef,
488 input: &DanfeInput,
489) -> Result<(), String> {
490 let y: f32 = 15.0;
491
492 layer.use_text(
493 "Consulte a autenticidade em: www.nfe.fazenda.gov.br/portal",
494 8.0,
495 Mm(50.0),
496 Mm(y),
497 font,
498 );
499
500 layer.use_text(
501 &format!("Chave: {}", format_chave(&input.chave_acesso)),
502 7.0,
503 Mm(40.0),
504 Mm(y - 5.0),
505 font,
506 );
507
508 Ok(())
509}
510
511fn draw_box(layer: &PdfLayerReference, x: f32, y: f32, width: f32, height: f32) {
514 let points = vec![
515 (Point::new(Mm(x), Mm(y)), false),
516 (Point::new(Mm(x + width), Mm(y)), false),
517 (Point::new(Mm(x + width), Mm(y + height)), false),
518 (Point::new(Mm(x), Mm(y + height)), false),
519 ];
520
521 let line = Line {
522 points,
523 is_closed: true,
524 };
525
526 layer.set_outline_color(Color::Rgb(Rgb::new(0.7, 0.7, 0.7, None)));
527 layer.set_outline_thickness(0.5);
528 layer.add_line(line);
529}
530
531fn draw_line(layer: &PdfLayerReference, x1: f32, y1: f32, x2: f32, y2: f32) {
532 let points = vec![
533 (Point::new(Mm(x1), Mm(y1)), false),
534 (Point::new(Mm(x2), Mm(y2)), false),
535 ];
536
537 let line = Line {
538 points,
539 is_closed: false,
540 };
541
542 layer.set_outline_color(Color::Rgb(Rgb::new(0.8, 0.8, 0.8, None)));
543 layer.set_outline_thickness(0.3);
544 layer.add_line(line);
545}
546
547fn draw_section_title(layer: &PdfLayerReference, font: &IndirectFontRef, title: &str, y: f32) {
548 layer.set_fill_color(Color::Rgb(Rgb::new(0.2, 0.2, 0.2, None)));
549 layer.use_text(title, 9.0, Mm(10.0), Mm(y), font);
550}
551
552fn format_chave(chave: &str) -> String {
555 let digits: String = chave.chars().filter(|c| c.is_ascii_digit()).collect();
556 digits
557 .chars()
558 .collect::<Vec<_>>()
559 .chunks(4)
560 .map(|c| c.iter().collect::<String>())
561 .collect::<Vec<_>>()
562 .join(" ")
563}
564
565fn format_cnpj(cnpj: &str) -> String {
566 let digits: String = cnpj.chars().filter(|c| c.is_ascii_digit()).collect();
567 if digits.len() == 14 {
568 format!(
569 "{}.{}.{}/{}-{}",
570 &digits[0..2],
571 &digits[2..5],
572 &digits[5..8],
573 &digits[8..12],
574 &digits[12..14]
575 )
576 } else {
577 cnpj.to_string()
578 }
579}
580
581fn format_cnpj_cpf(doc: &str) -> String {
582 let digits: String = doc.chars().filter(|c| c.is_ascii_digit()).collect();
583 if digits.len() == 14 {
584 format_cnpj(&digits)
585 } else if digits.len() == 11 {
586 format!(
587 "{}.{}.{}-{}",
588 &digits[0..3],
589 &digits[3..6],
590 &digits[6..9],
591 &digits[9..11]
592 )
593 } else {
594 doc.to_string()
595 }
596}