Robitex's Blog

Ideas in the web

Archivi Categorie: OpenOffice.org/LibreOffice

Libre Office: punto o virgola decimale?


Import dialog

Dialogo importazioni dati CSV

Ciao. Se ci troviamo a importare un file dati in un foglio di calcolo di Libre Office, per esempio attraverso un file CSV — comma separated values — è facile imbattersi nel “problema della virgola”.

Il problema della virgola

Questo inconveniente deriva dall’opposto uso del separatore decimale tra il mondo anglosassone e quello europeo continentale. La prima convenzione fa uso del punto e la seconda fa uso della virgola.
Quando importiamo un numero con il separatore è il punto — per esempio 95.50 — e la nostra versione di Libre Office è localizzata in italiano, esso è interpretato come testo.
La cella del foglio di calcolo così importata non potrà essere immediatamente utilizzata nelle formule.
In questa situazione gli utenti si arrangiano come possono con un cerca/sostituisci o con le più disparate formule.

Soluzione semplice

Ho scoperto che il problema della virgola si può risolvere in un istante e molto semplicemente.
Nel dialogo d’importazione che vi ho riportato sopra, basta infatti sostituire il tipo della colonna con i numeri con separatore decimale ‘.’ da ‘Standard’ a ‘Inglese US’, come vedete nell’immagine seguente.

Mi son sempre chiesto a cosa servisse il tipo di colonna ‘Inglese US’. Adesso lo so 😉

Import dialog and column type

Dialogo d’importazione con cambio di tipo colonna

Saluti.
R.

Annunci

Reinventare l’inventario


Articolo in formato PDF per la stampa

In cosa consiste un inventario?

Moltissimi esercizi commerciali alla fine dell’anno redigono l’inventario del magazzino. Si tratta di un elenco di oggetti dei quali se ne riportano le quantità rilevate ed il costo di acquisto dal fornitore.
Normalmente il report dell’inventario viene strutturato per reparti o per fornitori, oppure ancora per classi di articolo, riportando i relativi totali.
In generale un lavoro piuttosto noioso se non è supportato da software opportuni.

Una soluzione con il software libero

Nella maggior parte dei casi non si dispone di programmi di reporting, così ci si arrangia con un foglio di calcolo come OpenOffice.org Calc, utilizzando funzioni di subtotale per ottenere le somme parziali.
Per evitare errori e noiosi interventi manuali, in puro stile hacker, scriveremo un programma che costruisce un file e lo compila con LaTeX per produrre il documento relativo all’inventario nel formato pdf.

Inventario con Lua più LaTeX

Il programma elaborerà i dati in un formato di testo puro e LaTeX assumerà il ruolo di motore di reporting.
I programmi necessari alla soluzione presentata sono disponibili per un gran numero di sistemi operativi, solo che dovrete pensarci da soli ad installarli.

Ho scelto di scrivere il programma in Lua (versione 5.1), perché è un linguaggio di scripting che non necessita di compilazione, semplice ma potente, nato proprio con l’obbiettivo di svolgere elaborazioni di data description.
Per produrre il PDF ho scelto LaTeX perché elabora file di puro testo per fornire un output tipograficamente perfetto 🙂
Occorre quindi che sia Lua 5.1 sia una distribuzione TeX (per esempio TeX Live) siano installati sul vostro sistema.

Formato dei dati d’inventario

Le caratteristiche degli articoli del nostro magazzino sono peculiari. Per fissare le idee stabiliremo la seguente serie di campi abbastanza comuni tanto da consentire facilmente estensioni:

  • codice: identificativo univoco articolo
  • descrizione: breve testo descrittivo
  • fornitore: codice univoco del fornitore
  • reparto: codice univoco del reparto
  • costo: costo di acquisto di un pezzo
  • quantità: pezzi rilevati a magazzino

Caliamo direttamente questa struttura in un formato auto-descrittivo definito da un lista di elementi item simili al seguente, dove i dati sono racchiusi tra parentesi graffe e dove l’identazione ed i ritorni a capo sono opzionali (nella tastiera italiana le graffe si ottengono con le combinazioni Alt Gr + Shift + [ e Alt Gr + Shift + ] ):

item{code="C123",
     descr="NOME MODELLO - Classe",
     supplier="MYSUP",
     department="MYDEP",
     cost=123.45,
     qty=1,
     }

Come è evidente nell’esempio, i valori testuali sono racchiusi tra doppi apici mentre i valori numerici sono rappresentati con il punto decimale (non possiamo usare la virgola, simbolo già previsto dalla sintassi per la separazione dei campi (a proposito l’ultima virgola è opzionale)).
Possiamo anche inserire liberamente caratteri spazio, tranne che all’interno dei valori di tipo testo perché ovviamente diventano parte del valore stesso, così i due codici “C123″ e ” C123″ o “C 123” individuano due diversi articoli!

Nel formato key=value le parole chiavi sono sempre le stesse, così un blocco di dati potrebbe apparire come segue:

item{code="W1234", descr="W1234 - Quaderno", supplier="LOL", department="CRT", cost=   12, qty= 8}
item{code="Z4321", descr="Z4321 - Penna",    supplier="LOL", department="CRT", cost= 5.20, qty= 1}
item{code="Z5521", descr="Z5521 - Gomma",    supplier="LOL", department="CRT", cost= 2.80, qty=10}
item{code="Q0021", descr="Q0021 - Matita",   supplier="LOL", department="CRT", cost= 1.20, qty=80}
item{code="Q9921", descr="Q9921 - Penna",    supplier="OLO", department="CRT", cost=15.20, qty= 8}
...

Questo miscuglio di formato e sintassi è già interessante di per se e, considerando che le parentesi tonde che racchiudono gli argomenti di una funzione in Lua possono essere omesse se l’argomento è un unica tabella (od anche una stringa letterale), diventa anche codice Lua perfettamente eseguibile…

Le tabelle di Lua

In Lua esiste solo una struttura dati complessa con cui si implementano gli array, le liste e le strutture ad albero, chiamata tabella. Il linguaggio prevede che per creare un oggetto tabella si debba usare un costruttore che nella forma più semplice è {}.
Ciascun elemento di una tabella viene restituito specificando la chiave tra parentesi quadre od in dot notation come nel seguente esempio:

-- nb: il doppio trattino indica un commento
-- per prima cosa creiamo una nuova tabella e ne assegnamo
-- il riferimento ad una variabile locale
local mytab = {}

mytab["primo"] = 1
mytab["secondo"] = 2

-- dot notation:
print(mytab.primo)   --> stampa 1

mytab.terzo = mytab.primo + mytab.secondo

print(mytab.terzo)   --> stampa 3

local sec = "secondo"
print(mytab[sec])    --> stampa 2

Introdotte anche solo così velocemente le tabelle, si può intravedere il carattere del linguaggio Lua, semplice essenziale ma anche piuttosto elegante e potente.
Il formato dati ideato precedentemente rappresenta in Lua una serie di chiamate alla funzione item alla quale è passato come argomento il record dei dati dell’articolo d’inventario sotto forma di tabella.

Esercizio

Per prendere confidenza con le tabelle di Lua scriviamo una versione della funzione item perché restituisca il totale di magazzino in termini di costo, quantità e numero di articoli.

Intanto alcune note pratiche: apriamo il nostro editor di testo preferito (per Windows per esempio il Blocco Note), ed incolliamo nella relativa finestra i dati di esempio precedenti o quelli generati da un vostro foglio di calcolo. Salviamo il file con il nome di “inv.txt”.
Creiamo poi un secondo file chiamato “count.lua” in cui avremo inserito il codice seguente:

-- tabella codici articoli
local codeart = {}

-- variabili di conteggio
local totalcost = 0
local totalqty = 0
local numart = 0

function item(record)
    -- memorizzo il codice articolo in tabella
    -- in questo modo conteggio i codici
    -- non le chiamate ad item
    if not codeart[record.code] then
       codeart[record.code] = true
       numart = numart + 1
    end

    totalcost = totalcost + record.cost*record.qty
    totalqty = totalqty + record.qty
end

dofile("inv.txt")

print("Totali articoli: "..numart)
print("Costo totale: "..totalcost)
print("Quantita totale pezzi: "..totalqty)

A questo punto per stampare il conteggio d’inventario lanciamo da console il comando:

lua count.lua

Costruire l’albero ovvero mettere tabella dentro tabella

Ammettiamo che il magazzino sia suddiviso in reparti e che a sua volta i reparti siano suddivisi per fornitore, a cui apparterranno i relativi articoli. Questa struttura è un albero, e se rammentate come sono organizzati i vostri file nel disco rigido ne avrete un secondo esempio assai famigliare.
Bene, scriveremo la nostra funzione item in modo che costruisca l’albero corrispondente all’inventario di magazzino utilizzando una tabella che conterrà le tabelle dei reparti ciascuno contenente le tabelle dei fornitori, che ancora conterranno le tabelle degli articoli, traducendo esattamente la struttura con cui abbiamo deciso di organizzare il magazzino.

Presa confidenza con la struttura a livelli il codice non è ne difficile ne lungo:

-- nuovo oggetto tabella d'inventario
local store = {}

-- definizione della funzione item:
-- main data processing function
function item(record)
    -- livello principale: reparti
    -- creo per comodità una variabile che contiene l'ID
    -- del reparto
    local dep = record.department

    -- creo la tabella relativa al reparto se ancora non esiste
    if not store[dep] then
        store[dep] = {}
    end

    -- livello fornitore
    -- variabile di comodo che contiene il codice del fornitore
    local sup = record.supplier
    -- var di comodo per il riferimento alla tabella di reparto
    local tabdep = store[dep]

    -- se non esiste la tabella fornitore la creo
    if not tabdep[sup] then
        tabdep[sup] = {}
    end

    -- livello articolo
    -- vars di comodo alla tabella fornitore ed al codice articolo
    local tabsup = tabdep[sup]
    local codeart = record.code

    -- se il codice esiste aggiorniamo per quantità
    -- altrimenti creo nuova tabella articolo dove memorizzo
    -- la descrizione, il costo e la quantità
    if tabsup[codeart] then
        tabsup[codeart].qty = tabsup[codeart].qty + record.qty
    else
        tabsup[codeart] = {}
        local tabart = tabsup[codeart]
        tabart.descr = record.descr
        tabart.cost = record.cost
        tabart.qty = record.qty
    end
end

Raccolta dati

I vantaggi della struttura ad albero creata dalla funzione item sono due: oltre a classificare per livelli gli articoli è possibile inserire lo stesso articolo con successive chiamate alla funzione. Durante la raccolta dati infatti capita che pezzi diversi dello stesso articolo vengano registrati in tempi diversi.

La raccolta dati può essere effettuata connettendo un lettore di codici a barre ad un notebook e gestendo i dati con un foglio elettronico o meglio con un database. In questo modo l’inventario procede molto velocemente e senza fatica, a condizione che sugli articoli sia presente il codice a barre, tramite un etichetta per esempio, e che siano già stati inseriti i dati corrispondenti ai codici.

Visitare l’albero

La seconda parte del problema è generare il report. Occorre infatti visitare l’albero per trarne i totali dei vari livelli e produrre con essi il file sorgente LaTeX.
Per ragioni di tempo, terremo semplice il codice lasciando irrisolti alcuni problemi come un migliore utilizzo della potenza compositiva di LaTeX e l’ordinamento in ordine alfabetico delle categorie e classi del nostro magazzino.
Useremo per semplicità l’ambiente verbatim con impaginazione su due colonne, trascrivendo per ciascun articolo la descrizione, la quantità, il costo, ed il valore totale.

Chiamiamo renderTree la funzione Lua che visita l’albero iterativamente e scrive le righe in una tabella che, per motivi di performance, verrà trascritta nel file sorgente tutta in una volta. Useremo l’iteratore pairs per leggere i valori nelle tabelle dei vari livelli, iteratore che restituisce due argomenti la chiave ed il suo valore nella tabella indicata.

-- tabella per memorizzare le righe di output
local invLines = {}

-- larghezze in caratteri delle singole colonne
local descrCol = 25
local qtyCol = 8
local costCol = 10

-- formatta un intero
local function formatInt(n)
   local nn = string.format("%d",n)
   return string.rep(" ", qtyCol-#nn) .. nn
end

-- formatta un importo
local function formatDec( x )
   local xx = string.gsub(string.format("%.2f", x),"%." , ",")
   return string.rep(" ", costCol-#xx) .. xx
end

-- funzione di comodo per aggiungere elementi riga
local function addRow( s )
   invLines[#invLines+1] = s
end

-- funzione di formattazione riga articolo
-- .. è l'operatore di concatenazione stringhe
local function addItem(d, q, c)
   -- d descrizione
   -- q quantità
   -- c costo di un unico pezzo

   if #d > descrCol then
      d = string.sub(d,1,descrCol)
   else
      d = d .. string.rep(" ", descrCol-#d)
   end

   addRow(d.." "..
         formatInt(q).." "..
	 formatDec(c).." "..
	 formatDec(q*c)
	 )
end

-- funzione di formattazione riga totale
local function addTot( q, c )
    local d = "Tot." .. string.rep(" ", descrCol-4)
    addRow(d .. " " ..
           formatInt(q) .. " " ..
	   string.rep(" ", costCol+1)..
	   formatDec(c)
	   )
end

-- funzione principale creazione "vista"
local function renderTree()
   -- contatori generali
   local totstoreCost = 0
   local totstoreQty = 0

   local rule = string.rep("-",descrCol+qtyCol+2*costCol+3)

   -- ciclo principale per ciascun reparto
   for keydep, dep in pairs(store) do
      -- scriviamo l'intestazione di reparto
      addRow(rule)
      addRow("Reparto: " .. keydep)
      addRow(rule)
      addRow("")

      -- contatori di reparto
      local totdepCost = 0
      local totdepQty = 0

      -- ciclo per ogni fornitore del reparto
      for keysup, sup in pairs( dep ) do
          -- scriviamo una riga d'intestazione per il fornitore
          addRow("Fornitore :" .. keysup)
	  addRow(rule)

          -- contatori del fornitore
          local totsupCost = 0
          local totsupQty = 0

          -- ciclo sugli articoli del fornitore
          for keycode, code in pairs( sup ) do
	      addItem(code.descr, code.qty, code.cost)
              totsupCost = totsupCost + code.qty*code.cost
              totsupQty = totsupQty + code.qty
          end
          -- chiudo il fornitore scrivendone i totali
          addRow(rule)
          addTot(totsupQty, totsupCost)
          addRow(rule)
	  addRow("")

          -- aggiorno i totali di reparto
          totdepCost = totdepCost + totsupCost
          totdepQty = totdepQty + totsupQty
      end
      -- chiudo il reparto scrivendone i totali
      addRow(rule)
      addRow("Fine reparto")
      addTot(totdepQty, totdepCost)
      addRow(rule)
      addRow("")

      -- aggiorno i totali d'inventario
      totstoreCost = totstoreCost + totdepCost
      totstoreQty = totstoreQty + totdepQty
   end

   -- scrivo i totali di magazzino
   addRow(rule)
   addTot(totstoreQty, totstoreCost)
   addRow(rule)
end

Creare il file sorgente

Per questo basterà creare delle stringhe opportune che contengano le necessarie istruzioni LaTeX di preambolo e di chiusura racchiudendo il testo tra doppie parentesi quadre:

local docPreamble = [[
%
% this file was created by makeinv.lua script
% Copyright (c) 2010 Roberto Giacomelli
%

\documentclass[a4paper,11pt]{report}

\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[italian]{babel}

\usepackage[margin=1.6cm,bottom=2.5cm]{geometry}
\usepackage{inconsolata}

%
%
%
\begin{document}
\twocolumn
\scriptsize
\sffamily

\begin{verbatim}]]

local docEnd = [[
\end{verbatim}
\end{document}
]]

In questo modo il sorgente LaTeX è incastonato nel sorgente dello script. Naturalmente esiste la possibilità di prelevare il codice necessario da file esterni in modo da rendere il tutto più facilmente gestibile.
Poi sarà possibile costruire il file chiamato inventary.tex con il seguente codice:

local filename = "inventary"

-- create the LaTeX source file
local function makesource( fn )
	-- apertura file
	local f = assert(io.open( fn  .. ".tex", "w"))

	-- data assembly
	local s = table.concat(invLines,"/n")

	-- scrittura dati
	f:write(s)

	-- chiusura del file
	f:close()
end

Compilare il file con LaTeX

Certo, occorre una libreria in grado di interagire con il sistema operativo ed in Lua questa libreria si chiama os. Ecco il codice:

local function makepdf( fn )
       print("Start pdfLaTeX run...")
       os.execute("pdflatex " .. fn)
       os.remove(fn..".log")
       os.remove(fn..".aux")
       print("End")
end

Codice finale

Il codice finale si occupa di lanciare le funzioni per l’esecuzione del lavoro, eccolo:

-- execute all the item functions in the source datafile
dofile("inv.txt")

-- setup the start of the source LaTeX file
addRow(docPreamble)

-- make the pdf file of inventary
renderTree()

-- setup the end of the source LaTeX file
addRow(docEnd)

makesource( filename )
makepdf( filename )

-- end of script file

Lo script per intero

Assemblando le varie funzioni scritte il codice completo dello script è il seguente:

-- makeinv.lua
-- a script to make an invetary
-- Copyright (c) 2010 Roberto Giacomelli
-- see the page robitex.wordpress.com/legalese/ for licence details
-- enjoy

-- nuovo oggetto tabella d'inventario
local store = {}

-- definizione della funzione item:
-- main data processing function
function item(record)
    -- livello principale: reparti
    -- creo per comodità una variabile che contiene l'ID
    -- del reparto
    local dep = record.department

    -- creo la tabella relativa al reparto se ancora non esiste
    if not store[dep] then
        store[dep] = {}
    end

    -- livello fornitore
    -- variabile di comodo che contiene il codice del fornitore
    local sup = record.supplier
    -- var di comodo per il riferimento alla tabella di reparto
    local tabdep = store[dep]

    -- se non esiste la tabella fornitore la creo
    if not tabdep[sup] then
        tabdep[sup] = {}
    end

    -- livello articolo
    -- vars di comodo alla tabella fornitore ed al codice articolo
    local tabsup = tabdep[sup]
    local codeart = record.code

    -- se il codice esiste aggiorniamo per quantità
    -- altrimenti creo nuova tabella articolo dove memorizzo
    -- la descrizione, il costo e la quantità
    if tabsup[codeart] then
        tabsup[codeart].qty = tabsup[codeart].qty + record.qty
    else
        tabsup[codeart] = {}
        local tabart = tabsup[codeart]
        tabart.descr = record.descr
        tabart.cost = record.cost
        tabart.qty = record.qty
    end
end

-- tabella per memorizzare le righe di output
local invLines = {}

-- larghezze in caratteri delle singole colonne
local descrCol = 25
local qtyCol = 8
local costCol = 10

-- formatta un intero
local function formatInt(n)
   local nn = string.format("%d",n)
   return string.rep(" ", qtyCol-#nn) .. nn
end

-- formatta un importo
local function formatDec( x )
   local xx = string.gsub(string.format("%.2f", x),"%." , ",")
   return string.rep(" ", costCol-#xx) .. xx
end

-- funzione di comodo per aggiungere elementi riga
local function addRow( s )
   invLines[#invLines+1] = s
end

-- funzione di formattazione riga articolo
-- .. è l'operatore di concatenazione stringhe
local function addItem(d, q, c)
   -- d descrizione
   -- q quantità
   -- c costo di un unico pezzo

   if #d > descrCol then
      d = string.sub(d,1,descrCol)
   else
      d = d .. string.rep(" ", descrCol-#d)
   end

   addRow(d.." "..
         formatInt(q).." "..
	 formatDec(c).." "..
	 formatDec(q*c)
	 )
end

-- funzione di formattazione riga totale
local function addTot( q, c )
    local d = "Tot." .. string.rep(" ", descrCol-4)
    addRow(d .. " " ..
           formatInt(q) .. " " ..
	   string.rep(" ", costCol+1) ..
	   formatDec(c)
	   )
end

-- funzione principale creazione "vista"
local function renderTree()
   -- contatori generali
   local totstoreCost = 0
   local totstoreQty = 0

   local rule = string.rep("-",descrCol+qtyCol+2*costCol+3)

   -- ciclo principale per ciascun reparto
   for keydep, dep in pairs(store) do
      -- scriviamo l'intestazione di reparto
      addRow(rule)
      addRow("Reparto: " .. keydep)
      addRow(rule)
      addRow("")

      -- contatori di reparto
      local totdepCost = 0
      local totdepQty = 0

      -- ciclo per ogni fornitore del reparto
      for keysup, sup in pairs( dep ) do
          -- scriviamo una riga d'intestazione per il fornitore
          addRow("Fornitore: " .. keysup)
	  addRow(rule)

          -- contatori del fornitore
          local totsupCost = 0
          local totsupQty = 0

          -- ciclo sugli articoli del fornitore
          for keycode, code in pairs( sup ) do
	      addItem(code.descr, code.qty, code.cost)
              totsupCost = totsupCost + code.qty*code.cost
              totsupQty = totsupQty + code.qty
          end
          -- chiudo il fornitore scrivendone i totali
          addRow(rule)
          addTot(totsupQty, totsupCost)
          addRow(rule)
	  addRow("")

          -- aggiorno i totali di reparto
          totdepCost = totdepCost + totsupCost
          totdepQty = totdepQty + totsupQty
      end
      -- chiudo il reparto scrivendone i totali
      addRow(rule)
      addRow("Fine reparto")
      addTot(totdepQty, totdepCost)
      addRow(rule)
      addRow("")

      -- aggiorno i totali d'inventario
      totstoreCost = totstoreCost + totdepCost
      totstoreQty = totstoreQty + totdepQty
   end

   -- scrivo i totali di magazzino
   addRow(rule)
   addTot(totstoreQty, totstoreCost)
   addRow(rule)
end

local docPreamble = [[
%
% this file was created by makeinv.lua script
% Copyright (c) 2010 Roberto Giacomelli
%

\documentclass[a4paper,11pt]{report}

\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[italian]{babel}

\usepackage[margin=1.6cm,bottom=2.5cm]{geometry}
\usepackage{inconsolata}

%
%
%
\begin{document}
\twocolumn
\scriptsize
\sffamily

\begin{verbatim}
]]

local docEnd = [[
\end{verbatim}
\end{document}
]]

local filename = "inventary"

-- create the LaTeX source file
local function makesource( fn )
	-- apertura file
	local f = assert(io.open( fn  .. ".tex", "w"))

	-- data assembly
	local s = table.concat(invLines,"\n")

	-- scrittura dati
	f:write(s)

	-- chiusura del file
	f:close()
end

local function makepdf( fn )
       print("Start pdfLaTeX run...")
       os.execute("pdflatex " .. fn)
       os.remove(fn..".log")
       os.remove(fn..".aux")
       print("End")
end

-- execute all the item functions in the source datafile
dofile("inv.txt")

-- setup the start of the source LaTeX file
addRow(docPreamble)

-- make the pdf file of inventary
renderTree()

-- setup the end of the source LaTeX file
addRow(docEnd)

makesource( filename )
makepdf( filename )

-- end of script file

Applicazione

The final PDF document of example inventary

The final PDF document of example inventary

Scaricate il seguente file di prova chiamato appunto inv.pdf contenente 5000 articoli casuali. Purtroppo WordPress per ragioni di sicurezza non consente di fare l’upload di file con estensione txt per cui dovrete fare un passaggio in più copiando ed incollando i dati dal file in formato PDF (generato con SciTE).

Salvate lo script precedente in un file chiamato makeinv.lua (eventualmente assegnategli preventivamente i permessi di esecuzione), ed eseguitelo con il comando:

lua makeinv.lua

Otterrete questo file PDF in meno di un decimo di secondo!!!

Conclusioni

Lo script è veloce ed indipendente dal sistema operativo: legge un file di testo puro contenente i dati, li assembla nella struttura che corrisponde all’organizzazione reale del nostro magazzino e restituisce un file pronto per essere compilato da LaTeX.

Generando il sorgente LaTeX lo script può essere definito come un componente metaLaTeX. Tutti i dati sono testuali quindi trasparenti e facili da personalizzare. LaTeX poi produce un risultato tipografico eccellente.

L’idea si applica alla costruzione di report di dati complessi come un database cinematografico od un bilancio aziendale, oppure ad inventari ancora più complessi per esempio per una catena di negozi.

Bibliografia

Testo consigliato in assoluto per la programmazione in Lua è il PiL, scritto in maniera brillante dall’Autore stesso del linguaggio Roberto Ierusalimschy, pertanto non deve mancare nella nostra libreria.

Auguri

Tempo d’inventario, tempo di Natale e fine d’anno dunque…
Auguri a tutti!!

Impaginare un album di foto con LaTeX


La sfida

Ieri un collega mi ha chiesto come realizzare un album fotografico includendo circa 200 immagini nel formato jpeg, con quattro foto per pagina ciascuna con la propria didascalia di numerazione.

Il problema è interessante perché occorre scegliere gli strumenti informatici più idonei e sperimentare ottimizzazioni nel caso in cui servisse spesso elaborare documenti simili.

Gli strumenti

Abbiamo scartato quasi subito di lavorare con un word processor, sia perché volevamo una soluzione automatica, sia perché avevamo dubbi sulle capacità di gestire file di grandi dimensioni da parte di questo tipo di programmi.
Di conseguenza abbiamo escluso anche la soluzione con AutoCAD con l’impiego dello spazio carta. Avremo dovuto infatti inserire riferimenti raster e didascalie in modo troppo complicato e manuale.

Rimaneva di addomesticare la questione con LaTeX…

La battaglia ha inizio

Per prima cosa abbiamo copiato in una directory locale le foto, i cui file apparivano nominati sequenzialmente nel formato “DSC_0000”, come si può notare nell’immagine sottostante.

La directory delle foto aperta in Explorer

In un foglio di calcolo (OpenOffice.org Calc), grazie alla facilità con cui è possibile generare le serie (ricopia in basso), ho riempito una colonna dei numeri da 1 a 176. A fianco della prima cella ho inserito la seguente formula (anch’essa ricopiata in basso) che genera una stringa che rappresenta il nome del file nel formato richiesto, dato come argomento di una macro chiamata \foto (il foglio di calcolo avrebbe generato con poca fatica automaticamente e direttamente il corpo del sorgente LaTeX):

=CONCATENA("\foto{DSC_";TESTO(A1;"0000");"}")

Se la cella A1 contenesse il valore 122, la formula fornirebbe la stringa “\foto{DSC_0122}”. La funzione CONCATENA non fa altro che restituire un unica stringa a partire dai vari argomenti stringa separati da un punto e virgola, mentre la funzione TESTO trasforma un numero in una stringa nel formato specificato (in questo caso le quattro cifre fisse con zeri di riempimento).

A questo punto ho cancellato una serie di righe corrispondenti a foto che non avrebbero dovuto essere incluse perché non significative poi ho copiato l’area della seconda colonna e l’ho direttamente incollata nell’editor di testo, che a questo punto mostrava un sorgente di questo tipo:

\documentclass[a4paper,11pt,landscape]{report}
\usepackage{calc}
\usepackage{graphicx}
\usepackage[top=60pt,bottom=20pt,right=30pt,left=30pt]{geometry}

\pagestyle{empty}

\begin{document}
\centering
\twocolumn

\foto{DSC_0001}
\foto{DSC_0002}
\foto{DSC_0003}
\foto{DSC_0004}
\foto{DSC_0005}
\foto{DSC_0006}
% ecc ecc
\end{document}

Non rimaneva che dare la giusta definizione della macro \foto, che molto semplicemente prende l’argomento come il nome del file jpg da includere e come testo didascalico da posizionare al di sotto della foto:

\newcommand{\foto}[1]{%
\includegraphics[width=\columnwidth-60pt]{#1}

Foto #1\bigskip

}

Evoluzione della specie

Notando che la compilazione del sorgente richiedeva un tempo abbastanza lungo abbiamo inserito dopo le prime quattro foto il comando \end{document} lasciando inalterato il resto del documento. Ridotto in questo modo il documento ad una sola pagina, è stato rapidissimo settare per tentativi i giusti parametri dimensionali dei margini di pagina e la larghezza delle foto, per tornare poi all’inclusione totale cancellando il comando di chisura dell’ambiente document (il motore tipografico di LaTeX ignora qualsiasi cosa compaia dopo \end{document}).

Ottenuto il pdf finale, abbiamo provveduto a spedire il file alla stampante, ben contenta di ridestarsi dal suo torpore, ma come sempre se si tratta di linguaggio, ulteriori sviluppi del codice ci attendevano, e pure a lavoro finito.

La domanda è: e se avessimo voluto una didascalia con il semplice numero della foto e non il nome del file?

La risposta è: ridefinendo il comando \foto per esempio impostando la macro per ricevere il numero della foto da cui ricostruire il nome del file:

Per prima cosa definiamo un contatore ed una macro che imposta una seconda macro con il prefisso del nome dei file. Nel nostro caso imposteremo “DSC_”, così da poter gestire diversi valori assegnati di solito dal software della macchina fotografica digitale.

Aggiungiamo poi una serie di \if annidati per produrre il formato a quattro cifre a partire dal numero della file:

\newcount\filenum
\newcommand{\fileprefix}[1]{\def\myfileprefix{#1}}

\newcommand{\foto}[1]{%
\filenum=#1

\ifnum\filenum<10
  \def\myfilenumber{000\number\filenum}
\else
  \ifnum\filenum<100
    \def\myfilenumber{00\number\filenum}
  \else
     \ifnum\filenum<1000
        \def\myfilenumber{0\number\filenum}
     \fi
  \fi
\fi

\includegraphics[width=\columnwidth-60pt]{\myfileprefix\myfilenumber}

Foto n. #1\bigskip

}

Il corpo del sorgente si modificherà in:

\begin{document}
\centering
\twocolumn

% settaggio del prefisso del nome file:
\fileprefix{DSC_}

% inclusione delle foto
\foto{1}
\foto{2}
\foto{3}
\foto{4}
\foto{5}
\foto{6}
% ecc ecc
\end{document}

La domanda è: perché passare da un foglio di calcolo per generare il codice?

La risposta è: giusta osservazione, basta indicare solo il primo e l’ultimo numero della foto e LaTeX farà il resto:

A livello d’implementazione del nuovo comando \foto, occorre inserire un ciclo che, per ogni numero nell’intervallo specificato, inserisca la foto. Dal punto di vista sintattico invece, prevediamo di definire l’intervallo specificandone gli estremi all’interno di parentesi tonde e separati dal simbolo -> (utilizzeremo quindi la tecnica degli argomenti delimitati di TeX).

Ecco il listato del sorgente completo (abbiamo inserito macro con il carattere @ nel nome per renderle private):

\documentclass[a4paper,11pt,landscape]{report}
\usepackage{calc}
\usepackage{graphicx}
\usepackage[top=60pt,bottom=20pt,right=30pt,left=30pt]{geometry}

\makeatletter
\newcount\foto@numi
\newcount\foto@numf

\def\fileprefix#1{\def\file@prefix{#1}}

\def\insert@foto{%
\ifnum\foto@numi<10
  \def\file@num{000\number\foto@numi}
\else
  \ifnum\foto@numi<100
    \def\file@num{00\number\foto@numi}
  \else
     \ifnum\foto@numi<1000
        \def\file@num{0\number\foto@numi}
     \fi
  \fi
\fi

\includegraphics[width=\columnwidth-60pt]{\file@prefix\file@num}

Foto n. #1\bigskip
\advance\foto@numi by 1
}

\def\foto(#1->#2){%
   \foto@numi=#1
   \foto@numf=#2
   \advance\foto@numf by 1
   \loop
   \insert@foto
   \ifnum\foto@numi<\foto@numf
   \repeat
}
\makeatother

\pagestyle{empty}

\begin{document}
\centering
\twocolumn

\fileprefix{DSC_}
\foto(1->35)
\foto(38->48)
\foto(50->81)
\foto(83->122)
\foto(124->148)
\foto(152->157)
\foto(163->176)
\end{document}

Si può fare ancora di più, prevedendo una sintassi per l’esclusione di paricolari foto. Un solo comando che la implementasse basterebbe per definire l’intero album di foto!
Nel nostro caso potrebbe essere questo:

\foto{1->176 except 36,37,49,82,123,149->151,158->162}

Conclusioni

Il collega non aveva mai prima d’ora visto all’opera LaTeX. Forse è utile riflettere sulle impressioni di un assolutamente nuovo utente. Riporto solo una frase: “voglio imparare questo sistema!!” ha detto, e speriamo che l’ho faccia veramente.

Per quanto riguarda invece il giudizio sull’efficienza e l’efficacia con cui è stato possibile risolvere il problema dell’album fotografico con LaTeX, la mia opinione come vi immaginerete è molto positiva: precisione del posizionamento delle foto, facilità di regolazione dei parametri e separazione tra file jpeg e sorgente, solo benefici specifici di questo motore tipografico asincrono. Rimango in attesa della vostra opinione se vorrete affrontare lo stesso problema confrontando diversi strumenti software.

Alla prossima. Ciao.

Cucinare tabelle numeriche con verbatim


Impaginare lunghissime tabelle numeriche? Impossibile, ma…

Alcuni programmi forniscono in output un file in formato rtf, contenenti lunghissime tabelle numeriche che arrivano a centinaia di pagine. La soluzione più semplice per stampare questo particolare tipo di documento è elaborarlo con MS Word o Writer di OpenOffice.org.

Se tuttavia il numero delle pagine potesse essere fortemente ridotto con semplici accorgimenti d’impaginazione si incorrerebbe immediatamente in alcuni problemi nel metterli in pratica.

Per loro natura i word processor devono visualizzare a schermo il risultato dell’impaginazione quindi se devono occuparsi d’impaginare all’istante un’enorme tabella numerica con layout a più colonne per pagina, magari anche diverso per le pagine dispari e per quelle pari, cadono rendendo di fatto impossibile perseguire il proprio obiettivo.

E qui che LaTeX entra in scena, essendo appunto un compositore di testi asincrono, basta copiare l’enorme tabella in un foglio di calcolo, aggiungere una colonna con i due \\ di fine riga, esportare il tutto nel formato CSV con il carattere & come simbolo di separazione tra i campi, ed aggiungere i comandi LaTeX necessari in un veloce file di testo che darà il file pdf finale.

Ma, vi voglio descrivere una seconda procedura che ho messo a punto or ora: si tratta di esportare la tabella come se fosse testo normale, con l’incolonnamento reso da caratteri spazio che compensano la diversa lunghezza del testo nelle celle, per poi comporre il tutto con un font monospaziato tramite l’ambiente verbatim.

La procedura è più semplice della precedente, anche se si perde la possibilità automatica di ripetere la prima riga di descrizione ad ogni cambio di colonna.

Occorrente

Procedimento

Rilassatevi un po’, magari al pensiero di un buon caffè che non avreste potuto gustare nella necessità di stampare ed impaginare un documento di 650 pagine fronte retro.

Dunque, nel quieto clima autunnale, aprite il file sorgente con la megatabella, in OpenOffice.org (oppure in Word), selezionate la tabella come sapete fare voi e copiatela negli appunti (CTRL+C).

Aprite anche una sessione di Calc, il foglio di calcolo di OpenOffice.org, e date un comando Incolla speciale… (CTRL+Maiusc+V), scegliendo l’opzione “Testo non formattato”.

Special Paste in OpenOffice.org Calc

Incolla speciale in OpenOffice.org Calc

Nel dialogo “Importazione testo…”, selezionate la prima colonna e, tenendo premuto il tasto Shift, selezionate anche l’ultima. In questo modo tutte le colonne saranno selezionate. Assegnatele il tipo Testo e date l’ok.

In questo modo si evita il problema del punto decimale che viene interpretato come separatore di un valore temporale (ore.minuti anziché parteintera.partedecimale), ma se la vostra tabella non comporta questo problema, fate pure direttamente il CTRL+V in Calc.

Import taxt dialog...

Il dialogo Importa testo

A questo punto salvate il file e subito dopo date un “Salva con nome…” per esportarlo in CSV (Comma Separated Values). Scegliete infatti tale formato nel dialogo e spuntate l’opzione “Modifica impostazioni filtro” che appare se appunto selezionate il formato di salvataggio CSV.

Save dialog

State per salvare un nuovo file...

Eventualmente, scegliete l’opzione “Mantiene il formato corrente” se Calc ve lo chiedesse in alternativa a “Salva in formato ODF”, premendo il corrispondente pulsante, e nell’ultimo dialogo attivate la miracolosa opzione “Ampiezza colonna fissa”

Last step

Ultimo passo per la creazione del file

Avete appena prodotto il file di testo giustamente incolonnato con caratteri spazio pronto per essere composto con LaTeX.

Se il risultato non fosse perfetto e di vostro gradimento, ritornate a Calc ed aggiustate la larghezza delle colonne per riesportate il tutto, infatti il testo monospaziato a tabella è ottenuto tenendo conto della larghezza delle colonne del foglio di calcolo, e questa è l’unica eventuale scocciaturina.

Il risultato aperto con l'editor Scite

Il risultato aperto con l'editor Scite

Ultimo passo: cottura della tabella

Aggiunto un \begin{verbatim} come prima riga del file testo esportato e un \end{verbatim} come ultima riga, scrivete in un sorgente .tex il codice seguente (be è solo un esempio…), ed a questo punto, mettete a cuocere il file a fuoco lento con pdflatex (lento si fa per dire naturalmente).

\documentclass[a4paper]{article}
% Seleziona un font monospaziato adatto
\usepackage{inconsolata}

% setta il margine di pagina
\usepackage[margin=10mm]{geometry}

% elimina testatine e piè di pagina
\pagestyle{empty}

\begin{document}
% riduzione dimensione del font
\footnotesize
% impaginazione standard su due colonne
\twocolumn

% inserisce il contenuto del file CSV
\input{carichi}
\end{document}

Vi troverete con il pdf finale pronto per essere servito (intanto ecco il pdf finale che ho ottenuto per illustrarvi i vari passaggi condito a dovere, scaricatelo è un esempio di 9,8 KB!).

Buon Appetito!

%d blogger hanno fatto clic su Mi Piace per questo: