momente şi schiţe de informatică şi matematică
anti point—and—click

Un eseu de programare, cu PostScript

MetaPost | PostScript | cardioidă | cubice Bézier
2019 jul

[1] prezintă o experienţă proprie de investigare şi înţelegere a limbajului PostScript (PS), considerând că matematica elementară este inevitabilă şi asumând deprinderea de a folosi diverse programe utilitare şi interpretoare, având în vedere că PS este cumva "intim" legat de ecosistemul TeX.

Este firească transformarea seriei [1] (+[2]) într-un document PDF independent ("firească" măcar pentru că astfel, pot folosi fişierele imagine originale, în format PDF, în loc de copii-ecran PNG ale acestora); pe lângă aspectele "tehnice" specifice conversiei, avem de revizuit şi chiar de refăcut, anumite lucruri (inclusiv, imagini în format PDF).

Aspecte de natură tehnică, ale conversiei

Prima etapă este simplă - am comasat cele 8 fişiere HTML într-unul singur şi am folosit pandoc:

vb@Home:~/eseu$ pandoc -s -f html+tex_math_dollars -t latex \
                          -o eseuPS.tex asProgPS.html

obţinând fişierul LaTeX "eseuPS.tex"; compilându-l cu xelatex se obţine documentul PDF, dar împreună cu multe atenţionări - astfel că "eseuPS.tex" trebuie corectat şi completat corespunzător acestora.

Cele mai multe atenţionări ţin de "Overfull \hbox in paragraph"; despre ce este vorba? Secvenţele de cod-sursă existente în fişierul HTML erau plasate în elemente <pre>, iar acestea au fost convertite de către pandoc în anturaje LaTeX verbatim, fiind redate ca atare (fără nicio modificare a textului), folosind un font în care toate caracterele au o aceeaşi lăţime; ca urmare, multe dintre liniile de cod pe care existau şi comentarii sunt prea lungi pentru a încăpea în boxele orizontale asociate paragrafelor de text în pagina constituită de către compilatorul de LaTeX.

Pentru a asigura tipografierea convenabilă, într-o manieră unitară, a secvenţelor de cod într-un limbaj sau altul, putem folosi pachetul LaTeX listings; adăugăm în eseuPS.tex (în preambul):

\usepackage{listings}
\lstset{language=PostScript, basicstyle=\footnotesize, 
        texcl, breaklines, breakatwhitespace}

şi înlocuim toate apariţiile "verbatim" cu "lstlisting" (cel mai simplu, folosind meniul "Find and Replace..." din editorul de text, gedit, în care am deschis "eseuPS.tex"). Opţiunea "texcl" ne va permite să folosim comenzi LaTeX (inclusiv, notaţie matematică TeX) în textul-comentariu din codul-sursă.

Alte atenţionări se referă la imagini: File 'images/5mai-ell.png' not found. Însă ar fi ridicol să copiez fişierele *.png implicate de fişierul HTML iniţial, câtă vreme acestea erau doar copii-ecran după fişiere PDF deschise în evince ("Document Viewer"); formatul PNG era indicat pentru fişierele HTML [1] (de accesat printr-un browser), dar în eseuPS.tex este mai potrivit să includ direct fişierele-imagine în format PDF originale.

Pe parcursul lucrului vor fi de rezolvat şi diverse alte "aspecte tehnice". Aş menţiona aici doar chestiunea despărţirii în silabe. Compilatorul de LaTeX caută să formeze paragrafe de o aceeaşi lăţime; dacă, variind spaţiul dintre cuvinte (între nişte limite prestabilite), nu reuşeşte să producă rândurile paragrafului pe lăţimea fixată, atunci încearcă să despartă în silabe ultimul cuvânt din rândul curent.

În mod implicit, despărţirea corectă în silabe este asigurată pentru limba engleză şi eventual, pentru germană şi franceză (şi avem comanda \hyphenation{} prin care se poate specifica în preambul despărţirea în silabe pentru diverse cuvinte). Ca să rezolv definitiv problema separării în silabe pentru limba română, am instalat de la Ubuntu pachetul texlive-lang-european şi am adăugat în "eseuPS.tex": \usepackage{polyglossia} \setmainlanguage{romanian} (în preambul).

Este de observat însă că şabloanele sau regulile de despărţire în silabe încărcate astfel nu sunt chiar perfecte (sunt vizate circa 30 de limbi şi probabil, unele reguli sunt prea generale); de exemplu, cuvântul "instrucţiunile" apare despărţit greşit "instru-cţiunile" (şi a trebuit să corectez direct în text: "in-struc-ţi-u-ni-le"). Scapi totuşi de 99% din munca de a observa cuvintele de la capetele de linii şi a le despărţi eventual în silabe (repetând-o, dacă decizi ulterior să adopţi altă dimensiune de bază pentru font).

Corectarea unor aspecte de programare

Pe lângă reformulări şi reorganizări, au fost de făcut trei corecturi importante (şi bineînţeles că, în loc să adaug la [1] o "partea a opta" etc., am preferat să "rescriu" lucrurile, transformând în document PDF).

1

În planul de lucru din §1 ("partea întâia" din [1]) şi consecvent, în §5 ("partea a treia") introduceam fişierul "pack.eps" pentru a încapsula fişierul principal "grafic.eps" (care produce figura avută în vedere) şi fişierele "Label*.eps" (asociate etichetelor matematice de plasat pe figură); translaţiile necesare pentru poziţionarea etichetelor pe figură erau "calculate" ad hoc (şi nu într-o manieră generală, "prin program").

De fapt, nu este necesar un fişier nou, "pack.eps"; cel mai firesc este ca fişierele "Label*.eps" să fie încapsulate direct în fişierul "grafic.eps", iar procedând astfel putem folosi variabilele existente şi pentru a poziţiona corespunzător etichetele respective. Pentru un mecanism general de poziţionare a etichetelor (aplicabil cu cât mai puţine ajustări, oricărei figuri produse de "grafic.eps" şi etichete produse de fişierele "Label*.eps") trebuie să plecăm de la faptul că informaţiile necesare (locul din pagină unde este produsă iniţial - în momentul "încapsulării" fişierului - eticheta respectivă şi limitele acesteia) sunt deja înscrise în fişierul de încapsulat, anume pe linia marcată cu "%%BoundingBox"; deci "Label*.eps" trebuie şi citite, nu doar încapsulate (ceea ce mi-a scăpat din vedere în [1]).

Fie $(u,v)$ punctul din figura produsă de "grafic.eps" pe care vrem să-l etichetăm şi fie $[Lx\;\,Ly\;\,Ux\;\,Uy]$ valoarea "%%BoundingBox" citită din fişierul "Label.eps" care produce eticheta respectivă. Cele şase coordonate tocmai considerate sunt referite la sistemul de coordonate iniţial, cu originea în colţul stânga-jos al paginii şi cu "unitatea de măsură" $1\equiv 1\scriptsize bp$ (a 72-a parte dintr-un inch). Figura este construită (de obicei) în sistemul de coordonate iniţial şi apoi, va fi trasată efectiv în pagină după o anumită scalare - să zicem cu factorul $s$ (de exemplu $s=72\scriptsize bp$ $=1inch$) - şi o anumită translaţie, aducând originea iniţială la o distanţă convenabilă faţă de marginea stângă a paginii - să zicem de $a\cdot s\,\scriptsize bp$ - şi faţă de baza paginii, $b\cdot s\,\scriptsize bp$. Prin urmare, punctul de coordonate iniţiale $(u,v)$ va fi reprezentat pe pagină de punctul aflat la distanţa $(a+u)\cdot s\,\scriptsize bp$ de marginea stângă şi $(b+v)\cdot s\,\scriptsize bp$ faţă de baza paginii; iar în poziţia iniţială de încapsulare, eticheta are colţul stânga-jos la distanţa $Lx\,\scriptsize bp$ de marginea stângă şi $Ly\,\scriptsize bp$ de baza paginii.

Repoziţionarea etichetei, cumva lângă punctul corespunzător în pagină coordonatelor "abstracte" $(u,v)$ constă atunci în următoarele operaţii: translatăm colţul $(Lx, Ly)$ (şi implicit, întreaga etichetă) în colţul stânga-jos al paginii (prin -Lx -Ly translate, exprimând în PS); de aici, îl translatăm în punctul din pagină asociat cu $(u,v)$; în final, facem o mică translaţie de corecţie, încât eticheta să fie produsă puţin în stânga sau dreapta punctului, sau puţin deasupra ori dedesubt. Combinând, avem de modelat formule de genul: $-Lx + (a+u)\cdot s + corectie$ şi $-Ly + (b+v)\cdot s + corectie$, în care numai termenul "corectie" va trebui ajustat de la caz la caz, ţinând seama de lăţimea şi înălţimea boxei asociate etichetei ($Ux-Lx$ şi respectiv, $Uy-Ly$).

Redăm aici numai procedura de citire din fişierele "Label*.eps"; acestea erau produse automat (printr-un anumit script Bash), încât linia "%%BoundingBox:" apare într-o aceeaşi poziţie, anume pe linia a cincea din fişier. Până la înregistrarea colţurilor etichetei apar în total 166 de caractere; folosim operatorul file pentru a deschide în vederea citirii fişierul respectiv, folosim setfileposition pentru a poziţiona citirea de la al 166-lea caracter şi "citim" de aici 15 caractere (câte 3 cifre de colţ şi 3 spaţii intermediare); apoi folosim ( ) search pentru a depune pe stivă, pe rând, fiecare grup de câte 3 cifre consecutive, transformându-l imediat în număr întreg prin operatorul PS token:

/readBB {  % numele fişierului ("Label*.eps") este preluat din stivă
    /flb exch (r) file def
    flb 166 setfileposition  % unde găsim valoarea 'BoundingBox'
    flb 15 string readstring pop
    flb closefile  % pstack  % (şirul de 15 caractere este citit în stivă)
    3 { ( ) search pop  % parcurge până la primul spaţiu şi încarcă pe stivă
        token pop  % converteşte şirul de cifre în număr întreg
        3 1 roll pop pop exch } repeat
    token pop exch pop  % pstack  % (avem pe stivă cele 4 coordonate)
} bind def

Am folosit pop (de câteva ori), pentru a elimina din stivă informaţiile de care suntem siguri că nu avem nevoie; de exemplu, readstring şi token adaugă în stivă true sau false, semnalând că operaţia a reuşit sau nu, iar search adaugă în stivă şi partea din şir rămasă după subşirul căutat (şi am folosit 3 1 roll pentru a aduce în vârful stivei subşirul de 3 cifre respectiv, eliminând apoi celelalte subşiruri produse de search).

Aplicăm procedura 'readBB' fiecăruia dintre fişierele "Label*.eps", folosind forall:

[(Label6.eps) (Label5.eps) (Label4.eps) 
 (Label3.eps) (Label2.eps) (Label1.eps)] {
    readBB
    4 array astore  % tablou (``array") conţinând cele 4 coordonate
} forall  
% pstack % (cele 6 tablouri de coordonate)

Prin astore, cele 4 valori rezultate în stivă (la fiecare iteraţie) sunt comasate într-un tablou (încât vom putea accesa mai comod, colţurile respective). De observat că am transmis numele fişierelor "în ordine inversă", începând cu "Label6.eps"; astfel, cele 6 tablouri de coordonate vor fi aşezate în stivă în ordinea firescă în care urmează să le prelucrăm, începând (în vârful stivei) cu cel corespunzător lui "Label1.eps".

2

În [1] procedurile Ellipse, Kardioid etc. trasau efectiv curbele respective (sub gsave - grestore, fiecare); dar firesc era (şi mult mai "economic") ca acestea doar să producă "abstract" contururile respective, urmând ca trasarea efectivă (prin stroke) să fie făcută în "programul principal" (după scalarea convenabilă şi translatarea sistemului de coordonate).

3

În §5 ("partea a treia" din [1]), după ce încapsulam etichetele şi obţineam prin ps2pdf fişierul "grafic.pdf", modificam direct câmpul "/MediaBox" (iniţializat cu limitele standard ale paginii "A4"), înscriind valoarea necesară pentru "BoundingBox"; aceasta nu este tocmai corect, fiindcă numărul de caractere şi implicit, offset-urile informaţiilor din fişierul PDF se modifică, încât devine obligatorie şi corectarea tabloului de referinţe /XRef specific formatului PDF.

Metoda corectă constă în a folosi gs, desigur întâi cu −sDEVICE=bbox pentru a găsi BoundingBox şi apoi cu −sDEVICE=pdfwrite pentru a insera (prin mecanismul "pdfmark" din PDF) declaraţia /CropBox corespunzătoare cu BoundingBox, în fişierul "grafic.pdf".

Rezultatul PDF final

Pe lângă cele preluate din [1] şi [2], cu modificările evidenţiate mai sus şi cu o anumită reorganizare, am adăugat un "Appendix" conţinând câteva consideraţii matematice elementare asupra cardioidei şi semicardioidei.

Documentul PDF final rezultat este fişierul eseu-PS.pdf, cu următorul "cuprins":

Subliniem că nu este vorba de un "tutorial", ci mai degrabă de un eseu; elementele de limbaj PS nu sunt expuse în mod sistematic şi cu exemplificări simple (ca în tutoriale), ci sunt investigate pe măsură ce ele trebuie practicate pentru a avansa în realizarea proiectului propus iniţial - acela de a constitui o anumită figură geometrică (mai complicată decât un pătrat) şi a o eticheta astfel încât să poată fi integrată onorabil (plecând de la un fişier LaTeX) într-un document PDF.

vezi Cărţile mele (de programare)

docerpro | Prev | Next