{"id":1758,"date":"2011-05-01T18:08:57","date_gmt":"2011-05-01T16:08:57","guid":{"rendered":"http:\/\/yeti.albascout.ro\/blog\/?p=1758"},"modified":"2011-05-01T19:58:19","modified_gmt":"2011-05-01T17:58:19","slug":"reportlab-in-practica","status":"publish","type":"post","link":"https:\/\/yeti.albascout.ro\/blog\/reportlab-in-practica\/","title":{"rendered":"ReportLab \u00een practic\u0103"},"content":{"rendered":"<p>De\u0219i <em>online-ul<\/em> (cum \u00eei zic \u0103ia care se dau \u0219mecheri \u00een el) se \u00eent\u00e2mpl\u0103 \u00een mod normal \u00een fereastra unu browser web, se \u00eent\u00e2mpl\u0103 atunci c\u00e2nd construie\u0219ti aplica\u021bii web s\u0103 ai nevoie s\u0103 generezi materiale care pot fi printate. \u0218i aici ai, \u00een realitate, doar dou\u0103 op\u021biuni: <em>imagini<\/em> \u0219i <em>PDF, <\/em>pentru c\u0103 restul formatelor \u00een general depind de sisteme de operare, software instalat, fonturi instalate, e o nebunie care nu se mai termin\u0103.<!--more--><\/p>\n<p>Am f\u0103cut aplica\u021bii \u0219i pentru una \u0219i pentru alta. Generarea de imagini pentru printare este mai ales interesant\u0103 c\u00e2nd ai o cantitate ne\u00eensemnat\u0103 de text, c\u00e2nd documentul generat nu trebuie s\u0103 poat\u0103 fi selectat \u0219i c\u00e2nd nu te intereseaz\u0103 flow-ul elementelor (\u00een generarea de imagini \u00een general se lucreaz\u0103 cu pozi\u021bionare absolut\u0103). Spre exemplu, c\u00e2nd gener\u0103m ecusoanele pentru UP gener\u0103m imagini de rezolu\u021bie decent\u0103 care se pot printa (\u0219i c\u00e2nd foloseam PHP foloseam the allmighty <a href=\"http:\/\/php.net\/manual\/en\/book.image.php\">GD2<\/a> library, iar acum folosesc <a href=\"http:\/\/www.pythonware.com\/library\/pil\/\">PIL<\/a>).<\/p>\n<p>Totu\u0219i, atunci c\u00e2nd generezi documente \u00een care se lucreaz\u0103 cu mult text, \u0219i cu text a c\u0103rui dimensiune este greu de estimat, atunci \u00eencepe s\u0103 fie nevoie de o solu\u021bie care s\u0103 gestioneze flow-ul. Enter <a href=\"http:\/\/www.reportlab.com\/software\/opensource\/\"><em>ReportLab<\/em><\/a>, o bibliotec\u0103 OSS de <em>python<\/em> pentru realizat documente PDF. ReportLab are de toate, de la acces lowlevel pe un <em>Canvas<\/em> pe care po\u021bi desena orice, la implement\u0103ri complexe de diferite template-uri de documente, \u0219i de pagini individuale. Una<\/p>\n<p>De cur\u00e2nd am avut o sarcin\u0103 care implica generarea unui chestionar \u00een format PDF, dup\u0103 ni\u0219te reguli bine definite, \u00eentr-un format dat, \u0219i \u00een 12 ore am \u00eenv\u0103\u021bat mai multe mici chestii despre ReportLab.<\/p>\n<p>\u00cen primul r\u00e2nd, <a href=\"http:\/\/www.reportlab.com\/apis\/reportlab\/2.4\/\">documenta\u021bia online<\/a> este complicat de folosit \u0219i nu am reu\u0219it s\u0103 m\u0103 conving c\u0103 este la zi. \u00cen schimb, biblioteca fiind p\u00e2n\u0103 la urm\u0103 un generator de PDF-uri, documenta\u021bia pentru download (evident, format PDF) este complet\u0103 \u0219i ofer\u0103 c\u00e2teva exemple utile. Totu\u0219i, \u00eenceputul pare \u00eengrozitor de complicat. Pentru \u00eenceput mi-a fost de mare ajutor post-ul <a href=\"http:\/\/www.blog.pythonlibrary.org\/2010\/03\/08\/a-simple-step-by-step-reportlab-tutorial\/\">asta<\/a>.<\/p>\n<p><strong>Font-uri<\/strong><\/p>\n<p>O prim\u0103 problem\u0103 pe care o ai c\u00e2nd faci un document care folose\u0219te caractere Unicode (cum ar fi diacriticele rom\u00e2ne\u0219ti, indiferent dac\u0103 sunt cele corecte sau cele cu \u0219edil\u0103) e c\u0103 multe font-uri nu au toate glyph-urile (toate semnele) \u0219i te treze\u0219ti cu un document cu multe dreptunghiuri negre \u00een el.\u00a0 \u00cen Linux (sistem de operare care, chiar dac\u0103 nu ruleaz\u0103 pe calculatorul vostru, ruleaz\u0103 aproape cu sigran\u021b\u0103 pe server-ul pe care urmeaz\u0103 s\u0103 existe aplica\u021bia voastr\u0103), un font recomandat pentru suportul de caractere Unicode este <a href=\"http:\/\/dejavu-fonts.org\/wiki\/Main_Page\">DejaVu<\/a>.<strong> <\/strong><\/p>\n<p>Sunt c\u00e2\u021biva pa\u0219i aici pentru a avea acces apoi la font-urile astea \u00een cadrul aplica\u021biei.<\/p>\n<pre lang=\"python\">from reportlab.pdfbase.ttfonts import TTFont\r\nfrom reportlab.pdfbase.pdfmetrics import registerFontFamily, registerFont\r\n\r\nregisterFont(TTFont('DejaVu', '\/usr\/share\/fonts\/truetype\/ttf-dejavu\/DejaVuSerif.ttf'))\r\nregisterFont(TTFont('DejaVuBold', '\/usr\/share\/fonts\/truetype\/ttf-dejavu\/DejaVuSerif-Bold.ttf'))\r\nregisterFont(TTFont('DejaVuItalic', '\/usr\/share\/fonts\/truetype\/ttf-dejavu\/DejaVuSerif-Italic.ttf'))\r\nregisterFont(TTFont('DejaVuBoldItalic', '\/usr\/share\/fonts\/truetype\/ttf-dejavu\/DejaVuSerif-BoldItalic.ttf'))\r\nregisterFontFamily('DejaVu',normal='DejaVu',bold='DejaVuBold',italic='DejaVuItalic',boldItalic='DejaVuBoldItalic')<\/pre>\n<p><strong>Documente<\/strong><\/p>\n<p>Apoi, avem mai multe nivele de acces la construc\u021bia paginii. Exist\u0103 obiectul de baz\u0103, <em>Canvas<\/em>, \u00een care se poate desena, pe coordonate, practic oriunde (utile, spre exemplu, dac\u0103 scoatem o factur\u0103, o chitan\u021b\u0103, sau alt fel de tipizate). Totu\u0219i, de multe ori, avem nevoie s\u0103 creem documente \u00een care nu \u0219tim c\u00e2t text vom gestiona (spre exemplu, c\u0103r\u021bi, documente oficiale, procese verbale).<strong> <\/strong><\/p>\n<p>Pentru asta, ReportLab ne pune la dispozi\u021bie o clas\u0103 numit\u0103 <em>SimpleDocTemplate, <\/em>care are ni\u0219te <em>sensible defaults<\/em> pentru pagini. O chestie de re\u021binut este c\u0103 formatul implicit al\u00a0 paginii este A4.<\/p>\n<p>La creearea documentului, se pot seta \u0219i marginile \u0219i al\u021bi parametrii. \u0218i aici ajungem la ceva foarte important (\u0219i interesant) \u00een ReportLab, \u0219i anume cum se m\u0103soar\u0103 distan\u021bele. Cred c\u0103 exemplul de mai jos este gr\u0103itor pentru asta.<\/p>\n<pre lang=\"python\">from reportlab.platypus import SimpleDocTemplate\r\nfrom reportlab.lib.units import cm\r\n\r\ndoc = SimpleDocTemplate(buff, rightMargin = 1 * cm, leftMargin = 1 * cm, topMargin = 1.5 * cm,bottomMargin = 1.5 * cm)<\/pre>\n<p><strong>Flowables \u0219i paragrafe<\/strong><\/p>\n<p>Acuma, ReportLab pune la dispozi\u021bie ceea ce ei numesc Flowables, adic\u0103 elemente care <em>se a\u0219eaz\u0103 <\/em>unele dup\u0103 altele (<em>ca \u0219i cum ar curge<\/em>) \u00een document. Cel mai util exemplu este clasa <em>Paragraph<\/em>.<\/p>\n<p>Pentru creare, clasa <em>Paragraph <\/em>are nevoie de textul efectiv \u0219i de <em>stilul <\/em>cu care trebuie s\u0103 apar\u0103 \u00een cadrul documentului (despre <em>stiluri, <\/em>mai jos). Textul poate fi ca atare, dar ReportLab suport\u0103 un limbaj HTML basic, \u00een care putem s\u0103 introducem dimensiuni, familii \u0219i culori de font-uri (<strong>font<\/strong>), tag-uri pentru bold (<strong>b<\/strong>) \u0219i italic (<strong>i<\/strong>), linii noi (<strong>br<\/strong>) \u0219i alte asemenea &#8211; se g\u0103sesc \u00een ghid toate.<\/p>\n<pre lang=\"python\">from reportlab.platypus import Paragraph\r\n\r\nParagraph(u\"Lorem ipsum Integer  in tristique massa. Nunc accumsan.\", styles[\"Normal\"])<\/pre>\n<p><strong>Stiluri<\/strong><\/p>\n<p>Pentru a aranja cum arat\u0103 paragrafele, se pot folosi diferite stiluri. <em>SimpleDocTemplate<\/em> are o serie de stiluri, cum ar fi <em>Normal<\/em> din exemplul de deasupra, care pot fi modificate, sau la care se pot ad\u0103uga altele, \u00eentr-un workflow asem\u0103n\u0103tor celui din LibreOffice \/ Microsoft Office \u0219i stilurile de acolo.<\/p>\n<p>Paragrafele au o serie de atribute, cum ar fi font-ul, dimensiunea default, distan\u021ba \u00eentre r\u00e2nduri, distan\u021ba fa\u021b\u0103 de alte paragrafe, distan\u021b\u0103 fa\u021b\u0103 de marginea din st\u00e2nga \u0219i din dreapta, alinierea, existen\u021ba \u0219i culoarea unui border \u0219i a\u0219a mai departe.<\/p>\n<p>O chestiune care mi-a lipsit a fost posibilitatea de a pune border numai pentru o anumit\u0103 latur\u0103.<\/p>\n<pre lang=\"python\">from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle\r\nfrom reportlab.lib.enums import TA_JUSTIFY, TA_CENTER, TA_RIGHT\r\nfrom reportlab.lib.units import cm\r\n\r\nstyles = getSampleStyleSheet()\r\nstyles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY))\r\nstyles.add(ParagraphStyle(name='Inalt', fontName = \"DejaVu\", leading = 0.6 * cm, fontSize = 10))\r\nstyles.add(ParagraphStyle(name='Largplus', fontName = \"DejaVu\", leading = 0.8 * cm, fontSize = 10))\r\nstyles.add(ParagraphStyle(name='Right', fontName = 'DejaVu', fontSize = 12, alignment = TA_RIGHT, rightIndent = 1 * cm, leading = 0.6 * cm))<\/pre>\n<p><strong>Alte scheme cu flowables<br \/>\n<\/strong><\/p>\n<p>Dou\u0103 elemente bune care intr\u0103 tot la <em>Flowables<\/em> sunt <em>Spacer <\/em>\u0219i <em>PageBreak<\/em>. Spacer-ul are doi parametrii, distan\u021ba pe orizontal\u0103 \u0219i distan\u021ba pe vertical\u0103 (de\u0219i \u00een documenta\u021bie spune c\u0103 distan\u021ba orizontal\u0103 este ignorat\u0103).<\/p>\n<p>PageBreak nu are niciun parametru \u0219i face exact ce pare a face, \u00eencheie pagina \u0219i trece la o pagin\u0103 nou\u0103.<\/p>\n<pre lang=\"python\">from reportlab.platypus.flowables import PageBreak\r\nfrom reportlab.platypus import Spacer\r\n\r\nSpacer(1, 0.1 * cm)\r\nPageBreak()<\/pre>\n<p><strong>Imagini<\/strong><\/p>\n<p>Evident, se pot introduce \u0219i imagini. Pentru partea de imagini cred (nu am testat f\u0103r\u0103) c\u0103 este nevoie de PIL, \u0219i imaginea este \u0219i ea un Flowable<\/p>\n<pre lang=\"python\">from reportlab.platypus import Image\r\n\r\nImage(\"%s\/logo.jpg\" % \"\/var\/www\/yeti.albascout.ro\/images\")<\/pre>\n<p><strong>Tabele<\/strong><\/p>\n<p>Tabelele sunt o nebunie \u00eentreag\u0103. Principiul este simplu, e o list\u0103 de liste <em>python<\/em> pe baza c\u0103reia se construie\u0219te tabelul. Ce n-am \u00een\u021beles, de\u0219i am \u00eencercat un pic, este cum se stabilesc stilurile peste anumite celule. Un tutorial bun se poate g\u0103si <a href=\"http:\/\/www.blog.pythonlibrary.org\/2010\/09\/21\/reportlab-tables-creating-tables-in-pdfs-with-python\/\">aici<\/a>.<strong> <\/strong><\/p>\n<p>Con\u021binutul tabelului poate fi un text simplu, sau poate fi un Flowable sau o lista de flowables. Un alt lucru care nu l-am g\u0103sit din prima, este c\u0103 se pot pasa ca parametru cu nume \u0219i l\u0103\u021bimea coloanelor cu parametru <em>colWidths<\/em>.<\/p>\n<pre lang=\"python\">from reportlab.platypus import Paragraph, Image\r\nfrom reportlab.platypus.tables import Table, TableStyle\r\n\r\ndata = [[\"lorem\", Paragraph(u\"&lt;font&gt;&lt;b&gt;Ipsum&lt;\/b&gt;&lt;\/font&gt;\"],\r\n [\"sit\", Image(\"%s\/logo.jpg\" % \"\/var\/www\/yeti.albascout.ro\/images\")]]\r\n\r\ntable = Table(data, colWidths = [1.5 * cm, 15 * cm])\r\ntable.setStyle(TableStyle([('INNERGRID', (0,0), (-1, -1), 0.25, colors.black),\r\n                           ('BOX', (0,0), (-1,-1), 0.25, colors.black),]))<\/pre>\n<p lang=\"python\"><strong> <\/strong>Nebunia deosebit\u0103 st\u0103 \u00een cazul \u00een care spre exemplu vrei s\u0103 sco\u021bi pentru o anumit\u0103 celul\u0103 ni\u0219te border-uri, pe motiv c\u0103, spre exemplu, nu po\u021bi pune border doar pe-o anumit\u0103 latur\u0103 unui paragraf.<\/p>\n<p><strong>Construirea unui document<\/strong><\/p>\n<p>Ok, \u00een concluzie, exist\u0103 o serie de elemente. Dar cum se pun ele laolalt\u0103. \u00cen principiu este foarte simplu: se face un array cu ele, \u0219i apoi se paseaz\u0103 metodei<strong> <\/strong><em>build <\/em>a documentului.<\/p>\n<pre lang=\"python\">from reportlab.platypus.flowables import PageBreak\r\nfrom reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image\r\n\r\nnume_fisier = \"\/home\/yeti\/Desktop\/output.pdf\"\r\ndoc = SimpleDocTemplate(nume_fisier, rightMargin = 1 * cm, leftMargin = 1 * cm, topMargin = 1.5 * cm,bottomMargin = 1.5 * cm)\r\n\r\nStory = []\r\nimage = Image(\"%s\/logo.jpg\" % \"\/var\/www\/yeti.albascout.ro\/images\")\r\nparagraf = Paragraph(\"&lt;font size=9&gt;Lorem&lt;\/font&gt;\", styles[\"Normal\"])\r\n\r\nStory.append(image)\r\nStory.append(Spacer(1, 2 * cm))\r\nStory.append(paragraf)\r\nStory.append(PageBreak())\r\n\r\ndoc.build(Story)<\/pre>\n<p><strong>Footere \u0219i headere<br \/>\n<\/strong><\/p>\n<p>Se \u00eent\u00e2mpla de multe ori, mai ales c\u00e2nd trebuie s\u0103 respec\u021bi un anumit template tehnic, s\u0103 ai nevoie s\u0103 pui un header sau un footer. Cum header-ul \u0219i footer-ul sunt ni\u0219te elemente care nu intr\u0103 la categoria Flowables, se impune interven\u021bia direct pe Canvas-ul documentului. Totu\u0219i, ar fi de preferat ca lucrul \u0103sta s\u0103 nu ne afecteze abilitatea de a folosi SimpleDocTemplate. De aceea, metoda build a SimpleDocTemplate suport\u0103 mai mul\u021bi parametrii, cei mai interesan\u021bi fiind onFirstPage \u0219i onLaterPages<strong> <\/strong>, unde se pot specifica numele a dou\u0103 metode care s\u0103 fie apelate pe prima, \u0219i respectiv pe cel\u0103lalte pagini.<\/p>\n<p>Func\u021biile definite trebuie s\u0103 accepte doi parametrii care vor fi trimi\u0219i de <em>build<\/em>, unul va fi <em>Canvas-<\/em>ul paginii, iar cel\u0103lalt va fi documentul, \u00een cazul meu aici un <em>SimpleDocTemplate<\/em>.<\/p>\n<pre lang=\"python\">def laterPages(canvas, doc):\r\n '''\r\n Metoda scrie num\u0103rul paginii \u00een col\u021bul din dreapta jos pe o pagin\u0103 A4\r\n '''\r\n\r\n canvas.saveState()\r\n canvas.setFont('DejaVu', 8)\r\n canvas.drawString(18 * cm, 1 * cm, \"Pagina %d\" % (doc.page))\r\n canvas.restoreState()\r\n\r\ndoc = SimpleDocTemplate(\"\/home\/yeti\/Desktop\/output.pdf\")\r\nStory = []\r\n\r\n# aici se adauga elemente la doc\r\n\r\ndoc.build(Story, onLaterPages = laterPages)<\/pre>\n<p><strong>Integrare cu Django<\/strong><\/p>\n<p>Se \u00eent\u00e2mpl\u0103 s\u0103 vrem s\u0103 gener\u0103m nu un fi\u0219ier, ci un r\u0103spuns c\u0103tre browser-ul utilizatorului. Un motiv, spre exemplu, ar fi c\u0103 nu avem drepturi s\u0103 scriem local pe server. Altul ar fi timpul de r\u0103spuns &#8211; dac\u0103 ne permite memoria de pe server (altfel spus, dac\u0103 documentele sunt suficient de mici).<\/p>\n<p>Solu\u021bia este s\u0103 folosim StringIO, care ne permite s\u0103 creem obiecte file-like, dar direct \u00een memorie. So, o schi\u021b\u0103 de solu\u021bie ar putea suna a\u0219a:<\/p>\n<pre lang=\"python\">from StringIO import StringIO\r\nfrom django.http import HttpResponse\r\nfrom reportlab.platypus import SimpleDocTemplate\r\n\r\ndef trimite_pdf_ca_raspuns(request):\r\n  buff = StringIO()\r\n  nume_fisier = \"output.pdf\"\r\n  Story = []\r\n  doc = SimpleDocTemplate(buff)\r\n\r\n  # aici se adauga elemente in Story\r\n\r\n  doc.build(Story)\r\n\r\n  response = HttpResponse(mimetype='application\/pdf')\r\n  response[\"Content-Disposition\"] = 'attachment; filename=%s.pdf' % nume_fisier\r\n  response.write(buff.getvalue())\r\n  buff.close()\r\n  return response<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>De\u0219i online-ul (cum \u00eei zic \u0103ia care se dau \u0219mecheri \u00een el) se \u00eent\u00e2mpl\u0103 \u00een mod normal \u00een fereastra unu browser web, se \u00eent\u00e2mpl\u0103 atunci c\u00e2nd construie\u0219ti aplica\u021bii web s\u0103 ai nevoie s\u0103 generezi materiale care pot fi printate. \u0218i aici ai, \u00een realitate, doar dou\u0103 op\u021biuni: imagini \u0219i PDF, pentru c\u0103 restul formatelor \u00een &hellip; <a href=\"https:\/\/yeti.albascout.ro\/blog\/reportlab-in-practica\/\" class=\"more-link\">Continu\u0103 s\u0103 cite\u0219ti <span class=\"screen-reader-text\">ReportLab \u00een practic\u0103<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":1766,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[561,557],"tags":[609,747,746],"class_list":["post-1758","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-linux","category-facultate","tag-django","tag-pdf","tag-python"],"jetpack_featured_media_url":"https:\/\/yeti.albascout.ro\/blog\/wp-content\/uploads\/2011\/05\/python-logo.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/yeti.albascout.ro\/blog\/wp-json\/wp\/v2\/posts\/1758","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/yeti.albascout.ro\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/yeti.albascout.ro\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/yeti.albascout.ro\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/yeti.albascout.ro\/blog\/wp-json\/wp\/v2\/comments?post=1758"}],"version-history":[{"count":14,"href":"https:\/\/yeti.albascout.ro\/blog\/wp-json\/wp\/v2\/posts\/1758\/revisions"}],"predecessor-version":[{"id":1773,"href":"https:\/\/yeti.albascout.ro\/blog\/wp-json\/wp\/v2\/posts\/1758\/revisions\/1773"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/yeti.albascout.ro\/blog\/wp-json\/wp\/v2\/media\/1766"}],"wp:attachment":[{"href":"https:\/\/yeti.albascout.ro\/blog\/wp-json\/wp\/v2\/media?parent=1758"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/yeti.albascout.ro\/blog\/wp-json\/wp\/v2\/categories?post=1758"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/yeti.albascout.ro\/blog\/wp-json\/wp\/v2\/tags?post=1758"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}