Skip to topic | Skip to bottom
Home

Tesi
Tesi.EstensioniDiXSLFOr1.4 - 23 Feb 2005 - 12:32 - LucaFurinitopic end

Start of topic | Skip to actions

Introduzione

Il ciclo di vita di un libro

(processo di pubblicazione di un libro, con i vari soggetti coinvolti: autore - editore - grafico - stampatore) [...]

La diversità delle competenze dei soggetti coinvolti comporta molto spesso una diversità degli strumenti utilizzati: in ogni fase vengono usati programmi più specifici e professionali rispetto a quelli utilizzati nelle fasi precedenti. Questo pone grossi problemi di compatibilità e sincronizzazione: se è semplice, procedendo nel processo di pubblicazione, aggiungere informazioni in un documento utilizzando un programma e un formato più sofisticati, non è sempre possibile riportare queste stesse informazioni nei formati utilizzati in precedenza, nè visualizzare e modificare i file creati nelle fasi finali con gli stessi programmi usati in quelle iniziali. Di conseguenza, il modo più “comodo” che il grafico abbia per ottenere un feedback sul suo lavoro da autore ed editore consiste spesso nel distribuire copie cartacee delle bozze.

Inoltre, il processo non è sempre seguito in modo lineare dall’inizio alla fine: modifiche del contenuto (dalla correzione di refusi alla ristesura di interi brani) possono avvenire in un qualunque momento, precedente o addirittura successivo alla prima pubblicazione. Queste modifiche, che, per quanto appena detto, potrebbero essere comunicate sotto forma di correzioni a mano su un foglio stampato, pongono due diversi problemi:

  • da un punto di vista pratico e immediato, possono rendere necessaria la reimpaginazione di una porzione più o meno ampia del contenuto;
  • per quanto riguarda le conseguenze sul lungo periodo, quanto più tardi avvengono i cambiamenti, quanto più sono probabili “disallineamenti” dei dati, cioè differenze nelle diverse rappresentazioni che si hanno in ogni fase.

Ad esempio: la correzione di una parola scritta in modo errato non incide probabilmente che sulla riga in cui compare, ma l’inserimento o la cancellazione di un’intera frase possono avere effetti a catena che si ripercuotono anche sulle pagine successive; se ci si accorge di un refuso mentre il libro è già in mano al grafico, è possibile che esso venga corretto nel file finale ma non nel testo originale; in questo modo l’errore potrebbe ricomparire in successive riedizioni.

Il ruolo del grafico

Queste considerazioni evidenziano come l’attività del grafico rappresenti una fase critica del processo di pubblicazione, quasi un “punto di non ritorno” dopo il quale apportare modifiche rischia di essere costoso in termini di tempo e complicato per quanto riguarda la sincronizzazione dei dati.

Se da un lato è innegabile l’importanza del lavoro del grafico in molti ambiti, basti pensare ai libri d’arte o più semplicemente a quelli che contengono molte immagini, è tuttavia altrettanto evidente che ci siano casi in cui il suo contributo è di entità notevolmente minore.

Ogni volta che il contenuto è composto in massima parte da semplice testo, situazione in cui rientrano, ad esempio, la maggior parte dei saggi e dei romanzi, il suo ruolo si riduce infatti al prendere uno o più file in un formato “sorgente”, diffuso ma impreciso per quanto riguarda il layout, e utilizzando programmi professionali convertirli in un formato finale in cui possano essere espresse caratteristiche avanzate di impaginazione, che possa quindi essere usato per l’effettiva stampa.

Persino la messa a punto delle caratteristiche tipografiche (tipo e dimensione del carattere, spaziatura del testo e delle righe, ...), che pure richiede conoscenze specifiche ed influenza grandemente il risultato finale, non è sempre necessaria: molto spesso le scelte fatte per un documento possono, o addirittura devono, essere applicate a tanti altri, in modo che tutti abbiano lo stesso look.

È quindi lecito chiedersi se in questi casi, semplici rispetto al caso più generale ma comunque significativi, non sia possibile eliminare la figura del grafico.

I problemi da risolvere

Ovviamente, non è possibile semplicemente saltare a piè pari la fase di impaginazione, portando direttamente alla stampa il file ottenuto con un comune programma di videoscrittura: così facendo si otterrebbe quasi sicuramente o un layout scarso oppure, a costo di notevoli sforzi, un layout curato ma estremamente “fragile”, incapace di adattarsi a successivi cambiamenti.

Bisogna invece trovare degli strumenti che possano gestire questa fase del processo in modo automatico, rendendone possibile l’eventuale ripetizione a seguito di modifiche ed evitando la presenza di incongruenze nei diversi file utilizzati.

In particolare, per quanto riguarda l’impaginazione il principale problema è la corretta disposizione di orfani e vedove, cioè, rispettivamente, delle prime e ultime k righe di ogni paragrafo, con k solitamente uguale a 2.

Un buon algoritmo di page breaking deve tenere insieme questi gruppetti di righe: la presenza di una riga isolata a inizio o fine pagina è infatti sgradevole esteticamente e fastidiosa dal punto di vista della lettura. Una gestione sofisticata di orfani e vedove deve inoltre, ed è il compito più complesso perchè spesso in contrasto con quello precedente, fare in modo che nelle pagine così create il contenuto riempia perfettamente lo spazio a disposizione, la cosiddetta “gabbia tipografica”, senza lasciare aree vuote o sconfinare nel margine inferiore.

Anche per quanto riguarda la gestione e conservazione dei dati c’è bisogno di un buon compromesso tra esigenze contrapposte: da un lato, dovendo essere utilizzato nelle iterazioni iniziali del processo, il formato scelto deve permettere di apportare modifiche in modo molto semplice, sia nel contenuto che nelle caratteristiche di formattazione; dall’altro lato il formato deve essere “vicino” alla rappresentazione finale, così da poter conservarlo e ricreare in qualsiasi momento e in modo deterministico la versione da mandare in stampa.

In altre parole il formato deve essere estremamente ricco di informazioni di impaginazione, in modo che l’output dell’algoritmo di formattazione non dipenda, se non in minima parte e in dettagli trascurabili, dall’algoritmo stesso.

XSL Formatting Objects

Le caratteristiche richieste possono essere soddisfatte utilizzando, come linguaggio comune nelle diverse fasi del processo di pubblicazione, XSL FO, un vocabolario XML specificamente pensato per esprimere informazioni di formattazione.

I vantaggi che questa scelta offre sono notevoli:

  • essendo un formato puramente testuale, non ci sono vincoli sui programmi che è possibile usare per creare un file, visualizzarlo e modificarlo: è quindi possibile sfruttare un’ampia gamma di programmi già esistenti, da un semplice editor testuale a strumenti più sofisticati, senza il timore di compiere una scelta difficilmente revocabile o di ritrovarsi con file inutilizzabili;
  • essendo uno standard del W3C alla cui definizione hanno partecipato anche le principali software house del settore, è il frutto di sforzi congiunti difficilmente uguagliabili nella progettazione di una soluzione proprietaria, ed inoltre gode dei benefici dovuti al cosiddetto “network effect”: man mano che la sua diffusione aumenta, cresce il numero di programmi che lo utilizzano come input o come output;
  • nel caso in cui le proprietà o gli elementi previsti dallo standard non siano sufficienti per gli scopi che ci si prefigge, è possibile definire delle estensioni con la massima libertà e semplicità: tutto quello che si deve fare è modificare il formatter in modo che possa gestire correttamente i nuovi costrutti; un documento che contenga estensioni rimane un valido input anche per i programmi non modificati, che ovviamente ignoreranno ciò che non conoscono.

Carenze di XSL FO

Un aspetto che attualmente non viene sufficientemente approfondito dalle proprietà FO è il posizionamento verticale delle aree: ciò comporta, in particolare, l’impossibilità di controllare ed eventualmente modificare la distribuzione delle righe all’interno di una pagina.

Al fine di non creare orfani o vedove, e non potendo invadere i margini della pagina, la soluzione implementata dalle applicazioni esistenti consiste solitamente nel trattare le prime e le ultime righe come entità atomiche, non divisibili: se non è possibile disporre una di queste “pluri-righe” in una pagina perchè non è rimasto sufficiente spazio, essa viene spostata per intero all’inizio della pagina successiva. In questo modo, però, in fondo alla pagina precedente rimane uno spazio vuoto più o meno ampio, su cui non è possibile intervenire per mezzo di nessuna proprietà esistente.

Proprio su questo argomento verranno allora studiate ed implementate alcune estensioni.

Possibili scenari di utilizzo

Si possono individuare, a questo punto, diversi scenari di utilizzo per un interprete FO opportunamente modificato:

  1. documenti scritti direttamente nel namespace XML-FO;
  2. documenti XML, trasformati in documenti FO per mezzo di fogli di stile XSLT
  3. documenti Word, trasformati in documenti FO utilizzando IsaPress?.

File sorgente FO

Si tratta, per la verità, di una possibilità più teorica che pratica, dato che la prolissità della sintassi FO rende estremamente scomoda la creazione diretta di file molto più complessi del classico “hello-world.fo”.

Sarebbe inoltre faticoso modificare in un secondo momento alcune caratteristiche di formattazione, ad esempio cambiando l’aspetto di tutti i titoli, perchè comporterebbe un gran numero di sostituzioni lungo tutto il documento; allo stesso modo risulterebbe difficile riorganizzare la struttura del documento, ad esempio aggiungendo un indice o modificando l’ordine dei capitoli quando l’indice è già stato scritto.

File sorgente XML + XSLT

Il secondo scenario è invece molto più realistico e comune: il contenuto viene scritto direttamente in XML, oppure ad un testo esistente viene applicato il markup; attraverso una trasformazione XSLT viene successivamente rielaborato e convertito in formatting objects.

Questo modo di operare permette di separare, rendendole del tutto indipendenti, le caratteristiche del contenuto e quelle di presentazione: una volta stabilita la struttura, ad esempio definendo un DTD, documenti e fogli di stile potrebbero essere sviluppati in completa autonomia da soggetti diversi, ognuno dei quali potrebbe concentrarsi su un aspetto senza dover necessariamente essere competente nell’altro.

La separazione fra contenuto e formattazione permette di riusare separatamente i file, moltiplicando i possibili utilizzi: uno stesso documento XML può essere trasformato per mezzo di fogli di stile diversi e produrre così output differenziati, ad esempio presentazioni ottimizzate per media diversi (computer, cellulare, carta); dualmente, diversi file XML, purchè con la stessa struttura, possono essere elaborati utilizzando uno stesso stylesheet per ottenere output dall’aspetto estremamente omogeneo, caratteristica molto importante, ad esempio, per i volumi di una stessa collana.

Esistono molti programmi che permettono di creare interattivamente un foglio di stile, partendo da un file XML e definendo la struttura che si vuole ottenere e le caratteristiche di formattazione da applicare ad ogni elemento; anche così possono comunque essere necessarie alcune modifiche “manuali”, se si vogliono utilizzare proprietà e valori che non fanno parte dello standard.

Il punto debole di questo scenario rimane allora la creazione del documento XML: se il documento originario non contiene nessun tipo di markup non è infatti possibile aggiungerlo in modo automatico se non in modi estremamente banali.

Es.
in assenza di “vero” markup, è possibile convertire un semplice testo in XML racchiudendo in un elemento (ad esempio < documento > ... < /documento > ) tutto il contenuto, e creando un diverso elemento per ogni paragrafo (ad esempio < par > ... < /par > ); tuttavia è praticamente impossibile, per quanto possa essere elaborato il foglio di stile, trasformare un file così povero di informazioni in un output con un bell’aspetto.

File sorgente Word + IsaPress?

Infine, l’utilizzo di strumenti come IsaPress? permette di ottenere un file FO partendo da un file di testo elaborato con un diffuso programma di videoscrittura come Word, che contiene già sia le informazioni di contenuto che le principali caratteristiche di formattazione.

In questo modo non è necessario che l’autore originario abbia conoscenze di XML, nè che il markup debba essere aggiunto in un secondo momento da un diverso soggetto. Si tratta di un vantaggio non di poco conto, dal momento che l’eventuale inserimento del markup (cioè di informazioni) in un documento che ne è privo è un’operazione estremamente delicata, dalla quale dipende il risultato finale: assegnare questo compito a chi potrebbe avere una minor competenza sull’argomento trattato è quindi rischioso.

[...]

Ulteriori scenari di utilizzo

Esiste poi un’altra, inaspettata situazione in cui uno strumento con queste caratteristiche potrebbe risultare utile. Sebbene il tutto sia partito dal tentativo di eliminare, sotto alcune ipotesi semplificatrici, la figura del grafico, il risultato finale potrebbe essere uno strumento aggiuntivo a disposizione proprio del grafico, per la creazione di documenti dal layout complesso: in questo caso, l’applicazione non dovrebbe gestire l’impaginazione dell’intero documento, ma solo di alcune porzioni.

Il grafico potrebbe cioè concentrarsi sugli aspetti più “artistici” del documento (scelta e posizionamento di immagini, font, colori), definendo le aree in cui dovrà essere posto il testo e delegando al formatter FO il compito di disporlo. Questo permetterebbe anche di personalizzare automaticamente un documento, lasciando immutata l’impostazione generale e lasciando al formatter il compito di adattare i testi alternativi agli spazi a disposizione.

[...]

Line breaking

In questo capitolo verranno presentati in breve i più noti algoritmi di line breaking e le loro caratteristiche, in modo da poter poi discutere una loro estensione per risolvere il problema del page breaking.

Rappresentazione formale del problema

Verrà ora proposta la formalizzazione del problema utilizzata da Knuth; data la sua precisione nel rappresentare le caratteristiche del testo, la versatilità dei suoi costrutti e la semplicità delle sue regole, sarà successivamente utilizzata anche nell’ambito del page breaking.

È importante sottolineare come questa rappresentazione non ponga nessun vincolo implementativo: una volta che il contenuto da disporre sia stato codificato secondo questo formalismo, è possibile determinarne la suddivisione in righe utilizzando un qualsiasi algoritmo.

Rappresentazione dei paragrafi

Un paragrafo viene visto semplicemente come una sequenza

x1 x2 ... xn in cui ogni elemento xi può alternativamente essere di tipo:

  • box(w)

  • glue(w, y, z)

  • penalty(w, p, f)

Elementi box

Un elemento box è una scatola che contiene “qualcosa da scrivere”, sia esso un carattere, un ideogramma, un segno, una formula, ... L’effettivo contenuto del box non è rilevante: ai fini del line breaking è sufficiente conoscerne la larghezza w.

Elementi glue

Un elemento glue rappresenta uno spazio, di dimensione fissa o variabile; è caratterizzato dalla sua larghezza ottimale w, dalla sua estensibilità (stretchability, o semplicemente stretch) y e dalla sua comprimibilità (shrinkability o shrink) z. Ciò significa che ogni glue può assumere una dimensione qualsiasi all’interno dell’intervallo [w-z, w+y].

Elementi penalty

Un elemento penalty rapppresenta il “costo estetico” che si avrebbe terminando una riga in un certo punto; è caratterizzato da una larghezza w, un costo p e un valore di flag f.

La larghezza è da considerarsi solo nel caso in cui ci sia effettivamente una riga che termina in quella posizione. Corrisponde, in pratica, alla larghezza del trattino “-”: se tutti i possibili punti di sillabazione sono già indicati nel testo, la loro larghezza deve essere considerata solo qualora vengano davvero utilizzati.

Il costo è un valore all’interno dell’intervallo [- inf , + inf ]:

  • p = + inf significa che un’interruzione di riga in quella posizione è proibita;

  • 0 < p < + inf indica che la posizione non è ideale per terminare una riga, ma può essere usata se non ci sono alternative migliori;

  • p = 0 indica una posizione in cui è indifferente terminare o meno una riga;

  • - inf < p < 0 indica una buona posizione per terminare una riga;

  • p = - inf significa che è obbligatorio terminare una riga in quella posizione, o, in altre parole, non può esserci una posizione migliore.

Infine, il valore di flag è un booleano uguale a “vero” per quelle penalty che non dovrebbero essere usate per terminare righe adiacenti: ad esempio, dal momento che non è esteticamente gradevole un paragrafo in cui molte righe terminano con parole sillabate, i punti di sillabazione sono rappresentati da penalty con f = vero.

Convenzioni

All’inizio della rappresentazione di ogni paragrafo si aggiunge l’elemento

x1 = box(w1) che non rappresenta alcun segno, bensì l’eventuale indentazione della prima riga.

Dopo aver convertito il contenuto del paragrafo in box, glue e penalty, si accodano alla rappresentazione altri tre elementi:

xn-2 = penalty(0, inf , falso) xn-1 = glue(0, inf , 0) xn = penalty(0, - inf , falso) che rappresentano lo spazio vuoto al termine dell’ultima riga e un line break forzato.

Definizione di break

Suddividere il paragrafo in righe significa individuare una sequenza di break leciti

b0 < b1 < b2 < ... < bk Il primo break, b0, vale convenzionalmente 0 (il primo elemento del paragrafo ha indice 1); i successivi “veri” break sono indici bi di elementi del paragrafo tali che:

  • xbi è un elemento penalty con pbi < + inf , oppure xbi è un elemento glue e xbi-1 è un elemento box;

  • tutti gli indici bj degli elementi penalty con pbj = - inf sono compresi in questo insieme; in particolare, per costruzione bk = n.

Regole di soppressione

Una volta deciso dove le varie righe finiranno, è possibile definire dove dovranno cominciare; gli inizi di riga sono degli indici

a1 < a2 ... < ak tali che ai è il più piccolo intero compreso fra bi-1 e bi per il quale xai è un elemento box; in altre parole, ai è l’indice del primo box tra xbi-1 e xbi.

Il contenuto della i-esima riga è dato da

xai xai+1 ... xbi-1 [xbi] dove l’elemento xbi è compreso solo se si tratta di una penalty.

Mentre gli elementi box vengono tutti disposti nelle diverse righe, ci sono quindi due casi di soppressione di elementi glue e penalty:

  • se bj è un break, e xbj è un elemento glue, xbj viene soppresso;

  • tutti gli elementi glue e penalty compresi tra xbj-1 e xaj vengono soppressi.

L’algoritmo di line breaking generico

Con queste premesse, il funzionamento di un qualsiasi algoritmo di line breaking può essere schematizzato in questo modo:

inizializza() finchè (c’è contenuto da disporre) prendi una parte atomica del contenuto se (è un potenziale break) valuta il break() scegli i break definitivi() I vari algoritmi si differenziano per la funzione di valutazione: in particolare, possono variare

  • il numero di break provvisori mantenuti

  • le informazioni mantenute

  • i parametri utilizzati per scegliere il break definitivo tra quelli provvisori.

Algoritmi di line breaking semplici

...

First fit

È l’algoritmo più semplice. Ad ogni passo dell’algoritmo esiste un solo break provvisorio; l’unico parametro considerato è la lunghezza della riga a partire dall’ultimo break confermato.

...

Best fit

...

L’algoritmo di Knuth

...

Vantaggi e svantaggi di questi algoritmi

...

Dal line breaking al page breaking

Somiglianze e differenze

Il problema di disporre parole a formare linee e quello di disporre paragrafi per riempire pagine presentano forti somiglianze:

  • in entrambi i casi si ha a che fare con “oggetti” da disporre in ben determinato spazio: parole in righe in un caso, paragrafi in pagine nell’altro;

  • alcuni oggetti possono essere eventualmente divisi, ma solo in certe posizioni.

Si può quindi pensare di utilizzare uno stesso algoritmo per la creazione delle righe e per la creazione delle pagine.

Nel caso della giustificazione orizzontale, gli unici parametri su cui si può intervenire per aumentare o diminuire la lunghezza delle righe sono le dimensioni degli spazi tra parole e tra lettere. Le proprietà espresse nel documento definiscono un intervallo in cui questi parametri possono variare in modo continuo, ed è quindi possibile gestire questo tipo di variazioni in modo del tutto automatico.

Nell’ambito della giustificazione verticale si può analogamente variare la dimensione degli spazi tra paragrafi successivi e tra le linee di uno stesso paragrafo. C’è però tutta un’altra categoria di possibili interventi che possono modificare il numero di linee di uno o di tutti i paragrafi presenti in una pagina:

  • si può modificare il numero di linee in cui viene suddiviso un paragrafo semplicemente decidendo di non utilizzare la scelta giudicata “ottimale” dall’algoritmo di line-breaking, bensì una che prevede un numero minore di righe fitte, oppure un numero maggiore di righe più rade (essenzialmente, ciò significa intervenire sulle proprietà word-spacing e letter-spacing );

  • modificando le dimensioni o le proporzioni dei caratteri si ottiene una variazione dell’altezza delle righe e/o un possibile cambiamento nel numero di righe ( font-size , font-stretch );

  • se un paragrafo ha delle indentazioni (ad esempio un rientro nella prima riga, un rientro sinistro ...) è possibile variare il numero di linee occupate aumentando o diminuendo questo rientro; tali modifiche potrebbero coinvolgere più di un paragrafo: ad esempio, tutti gli elementi di un elenco devono avere gli stessi rientri;

  • è anche possibile intervenire su tutti i paragrafi presenti in una pagina, modificando la lunghezza delle linee (ampliando i margini a destra e a sinistra, diminuendo lo spazio fra colonne di testo, ...).

Oltre a queste maggiori possibilità di modifica, possono però essere presenti anche maggiori vincoli:

  • le proprietà orphans e widows indicano per ogni paragrafo il numero minimo di linee che è possibile disporre alla fine o all’inizio di una pagina

  • le proprietà keep ( keep-with-previous , keep-with-next e keep-together ) impediscono di separare paragrafi adiacenti, oppure le linee di uno stesso paragrafo, limitando le possibili soluzioni

  • le proprietà break ( break-before , break-after ) impongono la posizione di alcune interruzioni di pagina

In aggiunta a queste proprietà, esprimibili direttamente nel documento FO, possono essercene altre che attualmente non sono codificabili.

Ad esempio, un algoritmo che voglia produrrre output dall’aspetto elegante e professionale deve riuscire a mantenere l’allineamento delle righe su pagine affiancate: questa richiesta, che non è attualmente specificabile direttamente tramite una o più proprietà FO, impedisce di utilizzare lo spazio tra paragrafi per giustificare verticalmente le pagine, così come qualsiasi altro intervento che modifichi l’altezza delle righe di un solo paragrafo.

Inoltre, a differenza di quanto accadeva nella giustificazione orizzontale, alcuni dei parametri su cui è possibile intervenire agiscono in modo discreto anzichè continuo: aumentando o diminuendo il numero di righe utilizzate per disporre il contenuto di un paragrafo si provoca una brusca variazione delle sue dimensioni, pari all’altezza delle righe aggiunte o tolte. Questo complica notevolmente il problema di gestire la differenza tra la dimensione teorica della pagina e quella del contenuto in essa disposto.

In primo luogo, avere a disposizione una certa variazione potenziale, ad esempio dovuta alla possibilità di utilizzare una riga in più per un certo paragrafo, non dà la certezza di poter gestire una differenza di minore entità.

In secondo luogo, se si hanno a disposizione diversi parametri su cui intervenire, ad esempio il numero di righe di due paragrafi con una diversa dimensione del carattere e lo spazio tra di essi, la differenza non può essere semplicemente ripartita proporzionalmente tra i vari parametri, e l’ordine con cui si modificano i diversi parametri può influire sul risultato ottenuto, o addirittura sulla possibilità di trovare una soluzione. Se nel line breaking i risultati calcolati dall’algoritmo (differenza da gestire e coefficiente di variazione) erano immediatamente applicabili per ottenere la giustificazione orizzontale, nel page breaking questi valori devono essere successivamente trasformati in una serie di modifiche da apportare ai parametri a disposizione, prima di ottenere la desiderata giustificazione verticale.

Procedura astratta di page breaking

Data quindi la complessità intrinseca al problema, l’estrema varietà delle possibilità di intervento e la soggettività nel valutare esteticamente soluzioni alternative, è probabilmente poco realistico pensare di risolvere il problema per mezzo di un algoritmo universalmente valido; è più sensato cercare un algoritmo che:

  1. individui una distribuzione dei paragrafi in pagine che globalmente rispetti le proprietà widows, orphans, keep e break espresse nel documento fo, ed eventualmente necessiti di alcuni aggiustamenti locali alle pagine;
  2. riesca a gestire automaticamente tali aggiustamenti in casi semplici, ad esempio quelli in cui sia possibile intervenire in un unico modo;
  3. ricorra alle indicazione dell’utente per risolvere le situazioni più complesse; queste indicazioni vengono fornite utilizzando estensioni a XSL:FO, che definiscono nuove proprietà.

Suddivisione iniziale

.

Allineamenti verticali semplici

La proprietà FO che controlla l’allineamento verticale di aree all’interno di una reference area è display-align : in particolare, quando viene applicata al formatting object region-body determina il posizionamento del contenuto all’interno delle pagine.

Attualmente, i soli valori previsti sono:

$before
è il valore di default; il margine superiore della prima area deve coincidere con il margine superiore dell'area da riempire; $center: le aree devono essere disposte in modo che la distanza fra il margine superiore della prima area e il margine superiore della reference area sia la stessa che c’è tra il margine inferiore dell’ultima area e il margine inferiore della reference area; $after: il margine superiore della prima area deve coincidere con il margine superiore dell'area da riempire.

Queste opzioni permettono di spostare in alto, in basso o al centro dello spazio a disposizione tutto il contenuto di una pagina, senza però modificarne i rapporti interni: di conseguenza, lo spazio complessivamente lasciato vuoto rimane lo stesso in tutti i casi. Facendo un paragone con i possibili allineamenti orizzontali, questi corrispondono rispettivamente ai valori “start”, “center” e “end” della proprietà text-align .

Il modo più semplice per occupare tutto lo spazio consiste nel disporre le stesse aree distanziandole tra loro; in questo modo, quello che prima era spazio vuoto, al di sopra della prima area o al di sotto dell’ultima, viene ora ripartito fra le coppie di aree adiacenti. Questo tipo di allineamento verticale, che si potrebbe indicare con il nuovo valore “distribute”, corrisponde all’allineamento verticale giustificato.

Allineamenti verticali complessi

Distribuire lo spazio vuoto, o il restringimento necessario nel caso in cui la dimensione del contenuto superi quella a disposizione, tra tutte le aree è una possibile strategia, ma non certo l’unica né la migliore. Se i valori “top”, “center” e “bottom” mantengono immutate le aree da disporre e le loro posizioni relative, e “distribute” ne modifica la spaziatura, rimangono ancora da considerare tutte le possibilità di intervenire direttamente sulle aree.

Strategie diverse potrebbero quindi consistere nel modificare solo gli spazi tra paragrafi anzichè tutti quelli fra le righe, oppure intervenire sulla spaziatura del testo o sui margini per aumentare o ridurre il numero di righe occupate, oppure una combinazione di questi e altri interventi. Queste strategie complesse non hanno corrispondenti nell’allineamento orizzontale; per mantenere il paragone, le si può vedere come tentativi di giustificare il testo intervenendo non solo sugli spazi, ma anche sulla lunghezza delle parole.

La scelta dei parametri su cui intervenire dipende da due ordini di fattori: le caratteristiche specifiche del documento FO e le preferenze dell’utente.

Es.
Per quanto riguarda le caratteristiche, se il documento è un elenco di immagini non ha senso cercare di variare il numero di righe agendo su spazi o margini; modificare gli spazi tra paragrafi non è sufficiente se ci sono paragrafi tanto lunghi da riempire più di una pagina.

Es.
Aumentando la dimensione degli spazi o utilizzando righe più corte si ottengono circa gli stessi risultati: la scelta fra l’uno o l’altro intervento è di carattere principalmente estetico, e quindi soggettiva.

In definitiva, se gli esistenti valori per display-align possono essere già sufficienti in molte situazioni comuni, e la strategia “distribute” può portare a buoni risultati senza troppe complicazioni (per l’applicazione e per l’utente), esistono situazioni significative in cui per definire il risultato che si vuole ottenere e il modo per ottenerlo sono necessarie ulteriori estensioni: un valore aggiunto per display-align che potrebbe essere chiamato “fill” e una nuova proprietà fill-by-modifying per indicare i parametri su cui intervenire.

Rappresentazione minimale

Il primo passo da compiere è trovare un modo per rappresentare il problema in termini di “box”, “glue” e “penalty”, in modo da poterlo poi risolvere, almeno parzialmente, utilizzando l’algoritmo di Knuth o una sua variante.

Se si considera solo il numero “ottimale” di righe determinato dall’algoritmo di Knuth, la sequenza sarà composta semplicemente da un elemento box per ogni riga, di dimensione pari all’altezza della riga cui si riferisce, e da elementi penalty indicanti i punti in cui è possibile o meno interrompere il paragrafo.

Orfani e vedove

Le proprietà orphans e widows definiscono il numero minimo di righe che è possibile disporre, rispettivamente, nella prima e nell’ultima pagina su cui è diviso un paragrafo.

Affinchè la rappresentazione permetta di determinare solo break che rispettino queste proprietà è quindi sufficiente che fra gli elementi box rappresentanti le prime righe non ci sia nessun elemento penalty (oppure abbia valore infinito); analogamente per le ultime righe.

Es.
se la scelta giudicata migliore dall’algoritmo prevede la suddivisione di un paragrafo in sei linee, con widows = orphans = 2, la sequenza di elementi può essere: box(h1) + box(h2) + penalty(0, 0)+ box(h3) + penalty(0, 0) + box(h4) + penalty(0,0) + box(h5) + box(h6) dove hi è l’altezza della i-esima riga.

I box consecutivi sono del tutto equivalenti ad un unico box di dimensione pari alla somma delle dimensioni dei box sostituiti.

Spazi tra paragrafi

Le due proprietà space-before e space-after definiscono lo spazio sopra e sotto un blocco. Oltre alle componenti .minimum , .optimum , e .maximum , che definiscono la dimensione minima, ottimale e massima di questi spazi, esiste una componente .conditionality che indica il comportamento da tenere nel caso in cui lo spazio compaia all’inizio o alla fine di una pagina.che può i valori possibili sono:

$retain
lo spazio deve essere mantenuto anche in queste situazioni; $discard: lo spazio deve essere soppresso.

Questi spazi, se vengono definiti utilizzando .minimum < .optimum < .maximum , possono essere usati per giustificare verticalmente il contenuto di una pagina, nel caso in cui non si sia interessati a mantenere allineate le righe.

La codifica di queste proprietà in termini di box, glue e penalty è semplice:

  • se il valore della componente .conditionality è “discard” basta usare un elemento glue con width = .optimum , stretchability = ( .maximum - .optimum ) e shrinkability = ( .optimum - .minimum ); se l’algoritmo calcola un break in corrispondenza di questo elemento, o subito prima, esso sarà soppresso;

  • se invece il valore di .conditionality è “retain” si utilizza lo stesso elemento glue del caso precedente, opportunamente preceduto e seguito da altri elementi che ne impediscano la soppressione; in particolare, space-before sarà rappresentato dalla sequenza box(0) + penalty(0, inf ) + glue(x, y, z), e space-after da penalty(0, inf ) + glue(x, y, z) + box(0).

Proprietà break

Le proprietà break possono imporre un vincolo riguardante la posizione di un break e le aree generate dal formatting object in cui sono definite ( break-before ) o dal formatting object successivo ( break-after ); i valori che possono assumere sono:

$auto
il valore di default, che non impone nessun vincolo;
$column
le aree interessate dal vincolo devono essere le prime in una colonna; le proprietà con questo valore non riguardano direttamente la costruzione delle pagine;
$page
le aree interessate dal vincolo devono essere le prime di una pagina;
$even-page
le aree interessate dal vincolo devono essere le prime di una pagina pari; $odd-page: le aree interessate dal vincolo devono essere le prime di una pagina dispari.

In pratica, le proprietà break all’interno di una page-sequence sono l’analogo dei linefeed preservati all’interno di un nodo di testo: il loro unico effetto e’ suddividere il contenuto della page-sequence in porzioni indipendenti le une dalle altre. Se il loro valore è “even-page” o “odd-page”, può essere necessario inserire una pagina vuota.

Proprietà keep

Le proprietà keep ( keep-together , keep-with-next , keep-with-previous ) impongono dei vincoli che riguardano il posizionamento di aree all’interno di uno stesso contesto; le tre componenti .within-line , .within-column e .within-page specificano il vincolo nei possibili contesti: linee, colonne o pagine. Per quanto riguarda il problema del page breaking è ovviamente rilevante solo la componente .within-page .

Nel caso di proprietà keep-together , il vincolo riguarda il posizionamento delle aree generate dal formatting object per cui è definita; nel caso di keep-with-previous riguarda l’ultima area generata dal formatting object precedente e la prima generata da quello in cui la proprietà è definita; specularmente, nel caso di keep-with-next il vincolo riguarda l’ultima area generata dal formatting object in cui la proprietà definita e e la prima generata da quello seguente.

I valori che queste proprietà possono assumere sono:

$auto
il valore di default, che non impone nessun vincolo;
$always
il vincolo deve essere assolutamente rispettato, a meno che soddisfarlo non comporti la violazione di una proprietà break; $un numero intero: indica la “forza” del vincolo, che deve essere quindi soddisfatto purchè questo non comporti la violazione di una proprietà break o di una proprietà keep con forza maggiore.

Se tutte le proprietà hanno valori “always” o “auto” non è difficile codificarle nella sequenza di elementi:

  • una condizione keep-together può essere rappresentata eliminando gli elementi penalty interni al paragrafo, o mettendo a inf il loro valore;

  • una condizione keep-with-next viene rappresentata eliminando l’elemento penalty che segue la sequenza di elementi generata per il paragrafo;

  • una condizione keep-with-previous viene rappresentata eliminando l’elemento penalty che precede la sequenza generata per il paragrafo.

La situazione e' piu' complessa se sono presenti valori numerici diversi.

Una sola esecuzione dell'algoritmo di page breaking non sembra sufficiente. Un algoritmo di tipo first-fit o best-fit non ha una visione globale del documento, e le scelte fatte per le prime pagine potrebbero compromettere la possibilita' di rispettare condizioni keep nelle pagine successive. Stabilire una corrispondenza tra forza del keep e valore della penalita' (maggiore la forza del keep, maggiore il "costo" da pagare per infrangerlo) e utilizzare l'algoritmo di Knuth non servirebbe ad ottenere il comportamento prescritto, dato che l'algoritmo potrebbe preferire una soluzione che viola un keep di forza maggiore per rispettarne alcuni di forza minore.

Le specifiche sembrano suggerire un approccio incrementale alla ricerca della soluzione migliore: si considerano inizialmente solo i keep con maggior forza, ignorando gli altri, e se ne individua il massimo sottoinsieme soddisfacibile; successivamente si considerano i keep di forza immediatamente minore, e se ne determina il piu' ampio sottoinsieme che e' possibile soddisfare senza violare i keep di forza maggiore trovati in precedenza, e cosi' via per ogni distinto livello di priorita'.

Questa strategia potrebbe essere implementata richiamando piu' volte l'algoritmo di Knuth, modificando il valore degli elementi penalty utilizzati. Si inizia imponendo un valore alto, ma non infinito, alle penalty che rappresentano i keep di maggior forza, e zero alle altre: in questo modo vengono trovati i keep di massima priorita' che e' possibile soddisfare contemporaneamente; rendendo infinito il valore di queste penalty ci si assicura che anche nelle successive iterazioni questi keep verranno rispettati. Si prosegue in questo modo per ogni altro livello di priorita': si aumenta il valore delle penalty, si effettua il page breaking e si rendono vincolanti i keep rispettati.

Limiti di questa rappresentazione

In questi casi il mapping tra righe e sequenza di elementi è estremamente semplice; a causa però della mancanza di elementi elastici (a parte quelli dovuti eventualmente a spazi fra i paragrafi, che però impediscono il corretto allineamento delle righe) l’algoritmo di Knuth è quasi certamente destinato a fallire. Quindi:

  • se non è richiesto di riempire perfettamente le pagine, un algoritmo first-fit o best-fit può utilizzare questa rappresentazione per calcolare break che rispettino le proprietà keep e break, widows e orphans ;

  • se invece si desidera occupare tutto lo spazio a disposizione, le informazioni espresse da questa rappresentazione sono sufficienti solo nell’ipotesi (abbastanza remota) che sia possibile farlo utilizzando per ogni paragrafo il numero ottimale di righe.

Numero di righe variabile

L’algoritmo di Knuth applicato al line breaking produce i break definitivi al termine della scansione del paragrafo, dopo aver scelto, fra tutti i nodi attivi, quello con minori demeriti; se questa selezione non viene fatta, invece della sola scelta “ottimale” si hanno a disposizione tutte le possibilità di disporre il paragrafo trovate dall’algoritmo.

Si può ricercare allora una sequenza di elementi che riesca a rappresentare contemporaneamente tutte queste possibilità di disporre un paragrafo in righe: questa sequenza, invece di essere composta solamente da elementi di lunghezza fissa, comprenderebbe quindi anche elementi glue, che possono allungarsi o restringersi. Così facendo ci sono molte più probabilità che l’algoritmo riesca a trovare un opportuno insieme di interruzioni di pagina, calcolando per ognuna la differenza (positiva o negativa) tra la dimensione teorica della pagina e l’effettiva altezza complessiva degli oggetti che deve contenere, oltre al coefficiente da applicare agli elementi di dimensione variabile per raggiungere la dimensione desiderata.

Si suppone, al momento, che tutte le righe di un paragrafo abbiano la stessa altezza; le dimensioni degli elementi box, glue e penalty verranno quindi espresse utilizzando come unità di misura il numero di righe anzichè la loro effettiva altezza. La presenza di elementi inline di dimensioni maggiori, che provoca appunto una maggiore altezza delle righe in cui vengono disposti, sarà studiata più avanti in questa tesi.

Parametri

I parametri da cui dipende la sequenza di elementi da generare sono:

  • min , opt e max , rispettivamente il numero minimo, ottimale e massimo di righe in cui è possibile distribuire il contenuto del paragrafo; questi valori sono ottenuti eseguendo l’algoritmo di Knuth per il paragrafo in esame;

  • p e m , rispettivamente il numero di righe che è possibile aggiungere o togliere rispetto al valore ottimale; si tratta di valori immediatamente derivabili da quelli precedenti, con p = max - opt e m = opt - min , che permettono di indicare un paragrafo nella forma: opt (- m , + p );

  • orf e ved , rispettivamente il valore delle proprietà FO orphans e widows .

Casi possibili

Consideriamo allora un paragrafo il cui contenuto può essere disposto utilizzando opt (- m ,+ p ) righe, con m e p non entrambi nulli altrimenti si ritorna alla situazione in cui il numero di righe è fisso.

A seconda della posizione del valore orf + ved (il minimo numero di righe che è possibile separare) all’interno o all’esterno degli intervalli [ min , opt ] e [ opt , max ] è possibile distinguere quattro diversi casi:

  1. ( orf + ved ) <= min
  2. min < ( orf + ved ) <= opt
  3. opt < ( orf + ved ) <= max
  4. max < ( orf + ved )

Questi casi verranno ora discussi in dettaglio, per mostrare come per ognuno di essi sia possibile individuare una sequenza di elementi che rappresenti tutti e soli i modi in cui è possibile disporre il paragrafo.

Caso a)

È il più semplice caso in cui sia possibile dividere il paragrafo; in particolare, anche il numero minimo di righe consente una divisione tra più pagine.

Es.
per un certo paragrafo l’algoritmo individua quattro possibilità di disposizione, utilizzando 11, 12, 13 o 14 righe; la soluzione con meno demeriti è quella con 12 righe, quindi il numero di righe in cui è possibile sistemare questo paragrafo è 12(-1, +2). Se si ipotizza un valore per widows e orphans pari a 2, non è possibile separare le prime due righe e le ultime due, mentre tutte le altre posizioni sono valide per terminare una pagina.

Dato che le prime orf righe e le ultime ved non possono mai essere separate, si può pensare di rappresentarle usando degli elementi box, e di trattare le righe interne come un paragrafo a sè stante, ma con un valore per widows e orphans uguale a 1; in questo modo ci si è ricondotti a una situazione ancora più semplice.

È evidente che non si può rappresentare questo numero variabile di righe con la “classica” sequenza usata più volte: box( opt - ( orf + ved )) + penalty(0, inf ) + glue(0, p , m ) + box(0); questa rappresentazione consentirebbe sì di aggiustare il numero di righe, ma solo a patto di metterle tutte nella stessa pagina!

Es.
proseguendo l’esempio, questa rappresentazione dividerebbe il paragrafo in tre “sottoparagrafi” formati rispettivamente da 2, 8(-1, +2) e 2 righe; le interruzioni potrebbero avvenire solo fra questi sottoparagrafi, e le possibilità di suddividere il paragrafo su due pagine sarebbero solamente: tutte le 12(-1, +2) righe nella stessa pagina, oppure due righe in una e 10(-1, +2) in quella successiva, oppure 10(-1, +2) in una e le restanti due nella successiva.

Bisogna quindi decomporre la parte centrale del paragrafo in porzioni più piccole, ognuna delle quali potrà poi essere convertita in un’opportuna sottosequenza non interrombile di elementi. Si può allora pensare di ripartire la possibilità complessiva di espandersi o contrarsi tra le( opt - ( orf + ved )) righe interne; ognuna sarebbe quindi rappresentata utilizzando elementi che consentano una certa elasticità e il paragrafo risulterebbe decomposto in questo modo: orf + 1(- m /( opt - ( orf + ved )) , + p /( opt - ( orf + ved ))) + ... + 1(- m /( opt - ( orf + ved )) , + p /( opt - ( orf + ved ))) + ved .

Es.
il paragrafo con 12(-1, +2) righe verrebbe quindi decomposto in 2 + 1(-1/8, +1/4) + 1(-1/8, +1/4) + 1(-1/8, +1/4) + 1(-1/8, +1/4) + 1(-1/8, +1/4) + 1(-1/8, +1/4) + 1(-1/8, +1/4) + 1(-1/8, +1/4) + 2.

Il difetto di questa rappresentazione è che il potenziale aggiustamento attribuito ad ogni riga è in realtà privo di significato, essendo in generale un valore reale inferiore ad uno mentre il numero di righe deve essere un valore intero: sapere, ad esempio, che dopo la terza riga è possibile avere un ennesimo di riga in più o in meno è un’informazione del tutto inutile.

Dal momento che gli aggiustamenti hanno un’utilità pratica solo se assumono dei valori interi, invece di indicare un ennesimo di stretch e shrink dopo ogni riga intermedia, si potrebbe allora pensare di indicarne una unità intera dopo n elementi.

Es.
il paragrafo con 12(-1, +2) righe verrebbe quindi decomposto 2 + 1 + 1 + 1 + 1(+1) + 1 + 1 + 1 + 1(-1, +1) + 2.

Solitamente, le possibilità di restringere il paragrafo sono minori rispetto a quelle di allargarlo; considerando quindi dei valori complessivi di shrink pari a -1 o -2, si avrà un elemento con la possibilità di restingersi verso la fine della sequenza, e al massimo un altro a metà. Tuttavia non va dimenticato che questi elementi non rappresentano delle righe specifiche, ma solo dei “segnaposto”; è quindi è del tutto ininfluente la posizione, all’interno della sequenza, delle righe che hanno la possibilità di restringersi. Anche se nelle prime righe non si accumula nessuno shrink, è possibile ridurre lo spazio occupato da un paragrafo semplicemente anticipando il break; allo stesso modo, anche se non si accumula nessuna possibilità di allargarsi, niente impedisce di interrompere la pagina dopo la riga successiva.

Può sembrare, allora, che la soluzione migliore sia addirittura più semplice: basta indicare tutta la possibilità di restringersi o allargarsi nell’ultimo elemento prima delle ved righe finali.

Es.
il paragrafo usato fin’ora verrebbe quindi decomposto 2 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1(-1, +2) + 2.

Questa rappresentazione non è però completamente corretta, dato che le righe aggiuntive non sarebbero interrompibili. Non è cioè possibile, con questa rappresentazione, trovare una soluzione in cui si utilizza un numero di righe maggiore di quello ottimale, aggiungendono un po’ alla fine di una pagina e un po’ all’inizio della successiva.

Es.
nell’esempio usato fino ad ora, l’algoritmo di page breaking applicato alla sequenza così costruita non riuscirebbe a considerare il caso in cui il paragrafo viene suddiviso in 14 righe, 11 in una pagina e le restanti tre nella successiva.

In definitiva, la soluzione trovata per rappresentare queste situazioni consiste nel decomporre il paragrafo in porzioni di dimensioni fisse e porzioni con variazioni unitarie del numero di righe, queste ultime da porre subito prima delle ultime ved righe; in particolare, un paragrafo di opt (- m , + p ) righe verrà decomposto in:

orf

+ 1 ripetuto ( min - ( orf + ved )) volte

+ 0(+1) ripetuto p volte

+ 1(-1) ripetuto m volte

+ ved

In questa rappresentazione, le porzioni 0(+1) possono essere viste come “righe opzionali”, e le porzioni 1(-1) come “righe opzionalmente eliminabili”.

È importante che, a differenza delle variazioni in più, le possibili variazioni in meno non siano da sole, ma siano sempre legate a una riga, in modo che la dimensione complessiva sia comunque >= 0; se così non fosse, cioè se il paragrafo decomposto avesse delle componenti 0(-1), la rappresentazione non sarebbe corretta in quanto consentirebbe di avere meno di orf righe nella prima pagina, o meno di ved nell’ultima.

Es.
se il paragrafo 5(-1,+0), con orf = ved = 2, venisse decomposto in 2 +1 +0(-1) +2, un break dopo le prime due componenti consentirebbe di avere tre righe su una pagina e una sola su quella successiva; se la decomposizione fosse 2 +0(-1) +1 +2, un break dopo le prime due componenti consentirebbe di avere una sola riga nella prima pagina e tre nella successiva.

Per ipotesi:

orf + ved <= min *orf* + ved <= opt - m > m < opt - ( orf + ved ) cioè ci sono sempre sufficienti righe intermedie da legare alle variazioni in meno.

A questo punto, la conversione in elementi box, glue e penalty è semplice e immediata: per porzioni di dimensione fissa basta un semplice box, per porzioni elastiche della forma n(-k, +h) si utlizza la sottosequenza non separabile box(0) + penalty(0, inf ) + glue(n, h, k) + box(0). La risultante sequenza di elementi è quindi:

  • box( orf )

  • + penalty(0,0) + box(1) ripetuto ( min - ( orf + ved )) volte

  • + penalty(0,0) + box(0) + penalty(0, inf ) + glue(0,1,0) + box(0) ripetuto p volte

  • + penalty(0,0) + box(0) + penalty(0, inf ) + glue(1,0,-1) + box(0) ripetuto m volte

  • + penalty(0,0) + box( ved )

Caso b)

In questo caso il numero ottimale di righe consente di dividere il paragrafo, ma quello minimo no; il paragrafo ha cioè la possibilità di occupare qualche riga in meno, ma applicando il massimo restringimento non è più possibile interromperlo. In altre parole, se si divide il paragrafo si perde, totalmente o solo in parte, la possibilità di ridurne le dimensioni complessive.

Es.
un paragrafo 6(-1,+1), con orf = ved = 3, può occupare 5 righe in una pagina, 6 righe divisibili 3+3 o 7 righe divisibili 3+4 e 4+3.

Dato il basso numero di righe, potrebbe essere sensato assumere che m , il numero di righe eliminabili, sia 1, cioè min = opt -1 < opt = orf + ved ; in effetti, per valori di orphans e widows pari a due o tre, avere anche solo la possibilià di occupare due righe in meno significherebbe ridurre il numero di righe del 50 o del 33%. Tuttavia, si cercherà di trattare il caso più generale.

Una soluzione praticabile potrebbe essere quella di non considerare come possibili numeri di righe quelli che non permettono interruzioni all’interno del paragrafo, riconducendosi così al caso precedente.

Es.
proseguendo nell’esempio, si tralascerebbe quindi la possibilità di utilizzare 5 righe, ritrovandosi con un paragrafo 6(-0,+1) che, per quanto visto prima, verrebbe rappresentato dalla sequenza box(3) + box(0) + penalty(0, inf ) + glue(0,1,0) + box(0) + penalty(0,0) + box(3).

Se invece si vuole gestire questo caso nella sua interezza, bisogna comprendere bene in cosa differisce rispetto a quello visto in precendenza.

Prima, la possibilità di ridurre il numero di righe e quella di separarle erano indipendenti: ridurre il numero di righe non implicava limitazioni alla possibilità di separarle, nè viceversa interrompere il paragrafo poneva vincoli sul numero di righe. A riprova di questo, si può fare una semplice verifica sulla sequenza di elementi generata: se in corrispondenza di ogni possibile punto di interruzione si calcola lo shrink totale delle due sottosequenze risultanti da un eventuale break, la loro somma è pari allo shrink complessivo dell’intera sequenza, cioè m (stesso discorso per lo stretch, il cui totale è sempre p ); nessun elemento glue viene mai soppresso a causa di un break.

Ciò non può essere più vero nel caso in esame: anche se la sequenza, nel suo complesso, deve avere uno shrink pari a m , interrompendola si devono ottenere due sottosequenze con una minore (se non nulla) capacità di restringersi.

Questa osservazione suggerisce quindi come costruire la sequenza: se esiste un solo modo di dividere il paragrafo, cioè ( orf + ved ) = opt = max , basta rappresentare l’intera variazione in meno utilizzando un elemento glue posto immediatamente dopo all’elemento penalty. Se la penalty è utilizzata per interrompere il paragrafo, la successiva glue viene soppressa e non ha effetti.

Es.
per un paragrafo di 6(-1,+0) righe, le cui possibili disposizioni sono 5, 6, 3+3, la sequenza di elementi risulterebbe quindi box(3) + penalty(0,0) + glue(0,0,1) + box(3).

Se però esistono diverse possibilità di dividere il paragrafo, perchè ( orf + ved ) < opt oppure opt < max , questa strategia non è più sufficiente: utilizzando infatti una glue con valore di shrink m dopo ogni penalty si moltiplicherebbe lo shrink disponibile nel caso in cui il paragrafo non venisse diviso; anche suddividere in qualche modo lo shrink in corrispondenza delle varie penalty non funzionerebbe, perchè nel caso in cui si utilizzasse una penalty verrebbe soppressa solo una parte dello shrink. È quindi necessario trovare una diversa rappresentazione, adatta a questi casi più generali.

Nel caso in cui ( orf + ved ) = opt < max è possibile aumentare il numero di righe, e di conseguenza ci sono diversi punti in cui si può interrompere il paragrafo; tuttavia, è possibile diminuire il numero di righe solo se il paragrafo è interamente in una pagina. In altre parole, lo shrink complessivo della sequenza deve essere m , ma si deve annullare in tutte le sottosequenze originate in corrispondenza di ogni possibile punto di interruzione.

È possibile ottenere questo effetto sfruttando le proprietà di soppressione degli elementi, e utilizzando elementi glue con valori negativi di shrink:

  • box( orf ) + penalty(0, inf ) + glue(0,0, m ) [...]

  • + penalty(0, inf ) + glue(0,0,- m ) + penalty(0,0) + glue(0,0, m ) + box(1) ripetuto ( opt - ( orf + ved )) volte

  • + penalty(0, inf ) + glue(0,0,- m ) + penalty(0,0) + glue(0,0, m ) + box(0) + penalty(0, inf ) + glue(0,1,0) + box(0) ripetuto p volte

  • + penalty(0, inf ) + glue(0,0,- m ) + penalty(0,0) + glue(0,0, m ) + box( ved )

Le differenze rispetto alla sequenza generata nel caso a) sono in realtà meno di quante non sembrino a prima vista: le righe intermedie, quelle opzionali e le ultime ved hanno infatti la stessa rappresentazione. È invece cambiata la rappresentazione delle prime orf righe, e al posto delle penalty si usa la sottosequenza penalty(0, inf ) + glue(0,0,- m ) + penalty(0,0) + glue(0,0, m ).

Il primo elemento glue della sequenza, che non viene mai soppresso grazie all’elemento penalty che lo precede [...] , rappresenta l’intera shrinkability del paragrafo. Le coppie di glue che circondano ogni penalty hanno complessivamente una shrinkability nulla e non influiscono sul totale; se però la penalty viene scelta come break, la prima glue annulla quella posta all’inizio del paragrafo, mentre la seconda viene soppressa.

Es.
la sequenza che rappresenta il paragrafo con 6(-1,+1) righe risulta quindi essere: box(3) + penalty(0, inf ) + glue(0,0,1) [...] + penalty(0, inf ) + glue(0,0,-1) + penalty(0,0) + glue(0,0,1) + box(0) + penalty(0, inf ) + glue(0,1,0) + box(0) + penalty(0, inf ) + glue(0,0,-1) + penalty(0,0) + glue(0,0,1) + box(3).

Es.
riconsiderando l’esempio del paragrafo con 6(-1,+0) righe visto in precedenza otterremmo la sequenza box(3) + penalty(0, inf ) + glue(0,0,1) [...] + penalty(0, inf ) + glue(0,0,-1) + penalty(0,0) + glue(0,0,1) + box(3); per quanto più lunga, questa nuova sequenza è assolutamente equivalente a quella trovata in precedenza, dato che la sottosequenza penalty(0, inf ) + glue(0,0,1) [...] + penalty(0, inf ) + glue(0,0,-1) + penalty(0,0) non è interrompibile ed il suo effetto complessivo è lo stesso di una semplice penalty(0,0)

Per riuscire a gestire la situazione più generale possibile, rimane da considerare l’ipotesi in cui min < ( orf + ved ) < opt . In questo caso, la possibile variazione in meno è maggiore di uno, e diminuendo solo un po’ il numero di righe si ha ancora la possibilità di dividere il paragrafo:

m = opt - min *m* = opt - min + ( orf + ved ) - ( orf + ved ) m = ( opt - ( orf + ved )) + (( orf + ved ) - min ) La rappresentazione comprenderà quindi ( opt - ( orf + ved )) righe opzionalmente eliminabili, più uno stretch pari a (( orf + ved ) - min ) disponibile solo se il paragrafo non viene diviso:

  • box( orf ) + penalty(0, inf ) + glue(0,0, orf + ved - min ) [...]

  • + penalty(0, inf ) + glue(0,0,-( orf + ved - min )) + penalty(0,0) + glue(0,0, orf + ved - min ) + box(0) + penalty(0, inf ) + glue(0,1,0) + box(0) ripetuto p volte

  • + penalty(0, inf ) + glue(0,0,-( orf + ved - min )) + penalty(0,0) + glue(0,0, orf + ved - min ) + box(0) + penalty(0, inf ) + glue(0,0,1) + box(0) ripetuto opt - ( orf + ved ) volte

  • + penalty(0, inf ) + glue(0,0,-( orf + ved - min )) + penalty(0,0) + glue(0,0, orf + ved - min ) + box( ved )

Caso c)

In questo caso, utilizzando la disposizione ottimale del contenuto il paragrafo non è interrompibile; tuttavia è possibile dividerlo disponendone il contenuto su un numero maggiore di righe. In altre parole, interrompere il paragrafo comporta necessariamente un aumento del numero di righe.

Es.
un paragrafo può essere disposto su 5(-1,+1) righe, orf = ved = 3: il paragrafo potrebbe essere composto da 4 o 5 righe non interrompibili, oppure da 6, eventualmente interrompibili 3 + 3

È possibile risolvere questo caso riconducendosi mediante uno shift a quello precedente:

min <= opt < orf + ved <= max *opt* - m <= opt < orf + ved <= opt + p *opt* + p - p - m < orf + ved <= opt + p > opt’ - m’ < orf + ved < opt’ <= opt’ + p’ con

opt’ = opt + p *m’* = m + p *p’* = 0 Il paragrafo così modificato avrebbe le stesse possibilità di essere disposto, tuttavia la soluzione preferita, cioè quella che si ottiene con un coefficiente nullo, passerebbe da opt a opt’ . Ciò significa che nella situazione più favorevole e probabile, quella in cui il paragrafo è tutto in una stessa pagina, si utilizzerebbe una disposizione non ottimale, con spazi ampliati. È meglio allora ricercare una rappresentazione più fedele delle caratteristiche del paragrafo.

Es.
proseguendo l’esempio, dopo lo shift 5(-1,+1) -> 6(-2,+0) il numero di righe preferibile è 6, mentre prima era 5.

Per partire dalla situazione più semplice possibile, si può inizialmente ipotizzare che il paragrafo abbia min = opt , cioè sia possibile solamente aumentare il numero di righe, e che ( orf + ved ) = max : la caratteristica particolare di un paragrafo di questo tipo è che se viene disposto tutto nella stessa pagina ha una certa elasticità, ma se viene suddiviso la perde e deve necessariamente utilizzare il numero massimo di righe.

La differenza rispetto al caso precedente è che prima interrompere il paragrafo comportava solo la perdita della possibilità di restringerlo, mentre ora comporta il passaggio dalla possibilità di espanderlo all’obbligo di farlo.

Questa particolarità suggerisce quindi di posizionare un elemento glue che rappresenta l’elasticità immediatamente preceduto da un elemento penalty; questa volta, però, la penalty avrà una lunghezza pari allo stretch della glue; in questo modo, l’utilizzo della penalty come break ha l’effetto di aumentare le dimensioni del paragrafo e sopprimere la successiva glue.

Es.
la rappresentazione del paragrafo di 5(-0,+1) righe, con orf = ved = 3, può quindi essere box(2) + penalty(1, 0) + glue(0, 1, 0) + box(3).

La somma dei box dà la dimensione ottima del paragrafo; la lunghezza della penalty aggiunge al primo dei box quanto basta per farlo diventare uguale a orf .

A questo punto, si può facilmente tenere in considerazione anche l’eventuale possibilità di diminuire il numero di righe, sempre restando sotto l’ipotesi aggiuntiva ( orf + ved ) = max . Se già il numero ottimale di righe è insufficiente per interrompere il paragrafo, a maggior ragione lo sarà un numero inferiore; questa variazione in meno sarà quindi possibile solo se il paragrafo è interamente in una pagina, cioè nelle stesse condizioni in cui è possibile applicare una variazione in più. L’elemento glue già presente nella sequenza sarà allora utilizzato per rappresentare entrambi i tipi di variazione. La rappresentazione di un paragrafo di opt (- m ,+ p ) righe risulta essere: box( opt - ved ) + penalty( orf + ved - opt , 0) + glue(0, p , m ) + box( ved ).

Es.
la sequenza corrispondente al paragrafo di 5(-1,+1) righe è quindi: box(2) + penalty(1, 0) + glue(0, 1, 1) + box(3)

Questo vale, come detto, non per tutti i casi, ma solo per quelli in cui esiste un solo modo di dividere il paragrafo, utilizzando il numero massimo di righe.

Es.
per un paragrafo di 5(-0,+2) righe, con orf = ved = 3, le possibili disposizione sono: 5, 6, 7, 3+3, 3+4, 4+3; la sequenza corrispondente, secondo quanto appena visto, sarebbe: box(2) + penalty(1 0) + glue(0,2,0) + box(3), che in caso di divisione del paragrafo rappresenta solo la possibilità 3+3 e non 3+4 e 4+3.

Se invece opt < ( orf + ved ) < max è possibile dividere il paragrafo utilizzando un numero di righe maggiori di quello ottimale e minore di quello massimo; esistono più possibili punti di divisione, e parte dell’elasticità viene mantenuta anche nel caso in cui il paragrafo venga diviso:

p = max - opt *p* = max - opt + ( orf + ved ) - ( orf + ved ) p = ( max - ( orf + ved )) + (( orf + ved ) - opt ) Analogamente a quanto accadeva nel caso precedente, nella sequenza compariranno quindi ( max - ( orf + ved )) righe opzionali, mentre il rimanente stretch pari a (( orf + ved ) - opt ) verrà rappresentato dalle combinazioni di glue e penalty, che includeranno anche l’eventuale possibilità di ridurre il numero di righe:

  • box( opt - ved ) + penalty(0, inf ) + glue(0,( orf + ved - opt ), m ) + box(0)

  • penalty(0, inf ) + glue(0,-( orf + ved - opt ),- m ) + penalty( orf + ved - opt ,0) + glue(0,( orf + ved - opt ), m ) + box(0) + penalty(0, inf ) + glue(0,1,0) + box(0) ripetuti ( max - orf - ved ) volte

  • penalty(0, inf ) + glue(0,-( orf + ved - opt ),- m ) + penalty( orf + ved - opt ,0) + glue(0,( orf + ved - opt ), m ) + box( ved )

Es.
[...]

Es.
[...]

Caso d)

È in assoluto il caso più semplice: il paragrafo non è mai interrompibile, quindi per rappresentarlo basta la sequenza box( opt ) + penalty(0, inf ) + glue(0, p , m ) + box(0).

Es.
per un paragrafo di 4(-1,+1) righe, con orf = ved = 3, le possibilità di disposizione sono 3, 4 e 5; non è mai possibile dividerlo, e la sua rappresentazione sarà box(4) + penalty(0, inf ) + glue(0,1,1) + box(0)

Procedura di generazione

Avendo esaminato tutti i casi possibili, è ora possibile definire un algoritmo che, dati i vari parametri, crei la rappresentazione del paragrafo in termini di elementi box, penalty e glue. [...]

Le righe di ogni paragrafo, a seconda del loro numero minimo, ottimale e massimo e del valore delle proprietà widows e orphans , possono essere suddivise in una o più delle seguenti categorie:

$righe iniziali: non separabili a causa dalla proprietà orphans ; $righe interne: sempre presenti, non vincolate dalle proprietà widows e orphans ; $righe opzionali: utilizzabili per aumentare la dimensione del paragrafo; $righe eliminabili: utilizzabili per diminuire la dimensione del paragrafo; $righe opzionali condizionate: se il paragrafo non viene diviso, ulteriori righe a disposizione per aumentarne la dimensione, altrimenti un aumento obbligatorio; $righe eliminabili condizionate: se il paragrafo non viene diviso, ulteriori righe a disposizione per diminuirne la dimensione; $righe finali: non separabili a causa dalla proprietà widows .

La procedura di generazione degli elementi si compone quindi di tre fasi:

  1. si determina la suddivisione delle righe nelle varie categorie
  2. si crea la sottosequenza che fungerà da separatore
  3. si crea la sequenza di elementi componendo le sottosequenze relative ad ogni tipologia di righe, alternandole al separatore

Criteri di scelta

Considerare solo il numero ottimale di righe riduce quasi a zero le possibilità di creare pagine perfettamente riempite; tuttavia, considerare tutte le opzioni trovate dall’algoritmo può portare alla creazione di pagine perfette da un punto di vista teorico ma assolutamente inguardabili a causa dell’esagerata spaziatura del testo.

Avere a disposizione troppi elementi “elastici” può essere infatti inutile e addirittura controproducente: se questo grande aggiustamento potenziale non è necessario, il suo unico effetto è diminuire i coefficienti (e quindi i demeriti) corrispondenti ad aggiustamenti di minore, seppur rilevante, entità.

Es.
per un certo paragrafo, l’algoritmo di line breaking individua la possibilità di occupare due righe aggiuntive rispetto al numero ottimale; se non ci sono altri elementi aggiustabili, un page break che necessiti di una riga aggiuntiva avrà un coefficiente 0.5, e solo in caso di estrema necessità si sfrutterà anche la seconda riga aggiuntiva, con un coefficiente 1 e quindi un alto numero di demeriti. Se quel paragrafo è seguito da un altro con lo stesso potenziale aggiustamento, un page break che comportasse l’utilizzo di una riga aggiuntiva avrebbe un coefficiente 0.25 e quindi una bassa penalizzazione; tuttavia, dato che non si può aggiungere una mezza riga per ogni paragrafo, il danno estetico sarebbe lo stesso del caso precedente.

C’è inoltre un altro fattore da considerare: come già detto, l’algoritmo di line breaking utilizzato prova a calcolare i break chiamando l’algoritmo di Knuth con valori di tolleranza sempre più alti. Paradossalmente, questo può far sì che, dopo due tentativi falliti, il terzo abbia successo e trovi diversi possibili modi di disporre il contenuto; ovviamente, tutte queste opzioni si ottengono con altissimi coefficienti di aggiustamento degli spazi e sono quindi esteticamente sgradevoli: in questo caso non ha senso tenere in considerazione possibilità diverse da quella “meno peggiore”.

Bisogna quindi definire dei criteri di scelta, che permettano di selezionare, tra tutte le possibilità di disposizione calcolate dall’algoritmo di line breaking, solo quelle effettivamente utilizzabili.

Un possibile criterio consiste nel limitare il range di variazione del numero di righe ad un intervallo [opt-k, opt+k]; vengono cioè considerate solo le possibili disposizioni la cui distanza dal numero di righe ottimale è minore di un certo valore soglia. Un limite sensato è dato proprio dal valore delle proprietà orphans e widows (che solitamente è lo stesso, altrimenti si può considerare il massimo fra i due): a meno di condizioni keep particolarmente sfavorevoli, una variazione entro questo limite è sufficiente per gestire gli aggiustamenti nell’ambito di una pagina; una variazione maggiore potrebbe in teoria essere utile per gestire gli aggiustamenti di un pagina successiva, ma ciò dipende molto dai paragrafi seguenti.

Es.
[...]

Un criterio alternativo consiste invece nel considerate, oltre alla soluzione ottimale, le possibilità di disposizione i cui demeriti, calcolati dall’algoritmo, sono minori di un valore limite. L’efficacia di questo criterio dipende ovviamente dal valore limite utilizzato: esso deve necessariamente essere minore di quello utilizzato dall’algoritmo di line breaking, altrimenti non effettuerebbe alcuna selezione, ma non avendo ulteriori vincoli può essere determinato solamente attraverso analisi euristiche. Nel caso si utilizzi un valore troppo alto, il criterio non risulterà sufficientemente selettivo, e potrà portare alla creazione di pagine i cui paragrafi sono eccessivamente compressi o espansi; utilizzando un valore troppo basso sarà troppo selettivo e potrebbe portare al fallimento dell’algoritmo di page breaking di Knuth per mancanza di sufficienti elementi elastici.

[...]

Righe allineate

Condizioni di applicazione

Le variazioni al numero di righe possono essere applicate solo in modo discreto e non continuo, a differenza di quanto accadeva per le modifiche alla dimensione degli spazi. È quindi necessario fare in modo che l’algoritmo eviti di considerare come valida una soluzione che comporta una “variazione parziale” al numero di righe.

Una possibile strategia consiste nel fare un controllo sull’effettiva applicabilità degli aggiustamenti; questo controllo non può essere fatto a posteriori, dopo aver già calcolato i break point definitivi, dato che una soluzione nella realtà impraticabile potrebbe essere stata scelta a discapito di un’altra con maggiori demeriti, ma effettivamente attuabile.

Il controllo dovrebbe essere quindi fatto all’interno dell’algoritmo, in particolare nella procedura di valutazione, verificando l’attuabilità di ogni soluzione prima di creare un break provvisorio; una soluzione è attuabile se:

  1. la differenza è gestibile utilizzando solo misure “continue” OR
  2. la differenza è gestibile utilizzando solo variazioni al numero di righe OR
  3. la differenza è parzialmente gestibile utilizzando variazioni al numero di righe, e ci sono altri aggiustamenti a disposizione che permettono di compensare la differenza residua.

La sottocondizione a) è facile da verificare; se però è falsa bisogna controllare le altre due, che in generale non sono altrettanto semplici.

Verificare la sottocondizione b) significa infatti trovare una soluzione intera dell’equazione

h1x1 + h2x2 + ... + hkxk = d con i vincoli

-mi <= xi <= pi per i = 1, 2, ..., k dove d è la differenza da gestire, k il numero di altezze di riga distinte, hi l’altezza della riga, mi e pi rispettivamente il numero massimo di righe eliminabili e opzionali di altezza hi a disposizione nella pagina. A seconda del segno di d i vincoli possono essere semplificati: se d < 0 il numero di righe deve aumentare, e quindi 0 <= xi <= pi, mentre se d > 0 bisogna occupare meno righe, e di conseguenza -mi <= xi <= 0 [...] ; nonostante questo, tuttavia, se k > 1, cioè le righe non hanno tutte la stessa altezza, la complessità della verifica di questa condizione cresce rapidamente.

Se anche la seconda sottocondizione è falsa, infine, bisogna verificare la sottocondizione c): ciò significa trovare una soluzione intera a

d - pC <= h1x1 + h2x2 + ... + hkxk <= d + mC con gli stessi vincoli e dove mC e pC sono rispettivamente la massima variazione continua in meno e in più; in realtà, invece che trovare una soluzione sarebbe meglio trovarle tutte e decidere in qualche modo quale sia quella migliore.

In definitiva, introdurre un controllo di questo tipo all’interno dell’algoritmo sarebbe molto complesso e, soprattutto, avrebbe un costo computazionale estremamente elevato.

D’altra parte, le situazioni che potrebbero non essere vengono gestite correttamente, cioè quelle in cui l’algoritmo di page breaking potrebbe trovare una soluzione che comporta una variazione non intera nel numero di righe di un paragrafo, sono particolari e definibili in modo molto preciso; esse si verificano infatti quando:

  1. si vuole riempire esattamente la pagina AND
  2. si vuole intervenire (anche) sul numero di righe dei vari paragrafi AND
  3. esiste una classe di paragrafi le cui righe devono rimanere allineate AND
  4. sono presenti anche oggetti (righe, immagini, ...) di altezza diversa rispetto a quella “normale”.

Se la condizione a) è falsa non è richiesta nessuna ottimizzazione delle pagine: si può quindi utilizzare la rappresentazione minimale con un algoritmo di tipo first-fit o best-fit.

Se è falsa la condizione b) si elimina il problema alla radice, dato che considerando solo il numero ottimale di righe si forza l’algoritmo a considerare solo modifiche di tipo continuo; se però nelle pagine non sono presenti altri elementi di dimensione variabile è improbabile che si riesca a soddisfare la prima condizione.

La condizione c) limita notevolmente i parametri su cui è consentito intervenire per ottenere l’ottimizzazione della pagina; se infatti fosse falsa, si avrebbe a disposizione anche lo spazio fra i paragrafi e quello fra le righe di uno stesso paragrafo. Questa condizione ha come prima logica conseguenza che tutte queste righe hanno la stessa altezza, che verrà chiamata altezza di riferimento: in caso contrario non avrebbe senso parlare di righe allineate. Inoltre, questa altezza deve corrispondere a un ennesimo dell’altezza complesiva a disposizione: se si vuole riempire perfettamente la pagina, e la variazione del numero di righe è l’unico (o comunque il principale) intervento possibile, è infatti necessario che il documento FO sia stato adeguatamente progettato e l’altezza “normale” delle righe sia un divisore esatto dell’altezza della pagina; se così non fosse, neppure la pagina più semplice possibile (una in cui ci siano solo righe di dimensioni standard senza proprietà keep, widows o orphans ) potrebbe essere ottimizzata. Sotto questa ipotesi e in mancanza di oggetti di diversa altezza le soluzioni trovate dall’algoritmo sono quindi sempre attuabili, perchè la differenza tra altezza della pagina e altezza del contenuto non può che essere un multiplo esatto dell’altezza di riferimento.

La condizione d) è quella che introduce le difficoltà: la presenza di oggetti di dimensioni anomale in una pagina provoca infatti una differenza che, a sua volta, non è multipla dell’altezza di riferimento, e quindi non è gestibile modificando il numero di righe dei paragrafi normali. È quindi necessario che questi oggetti anomali siano preceduti e seguiti da “elementi di raccordo”, in modo che la loro dimensione complessiva sia un multiplo esatto dell’altezza di riferimento.

Una strada alternativa per risolvere queste situazioni è allora quella di modificare non l’algoritmo, bensì la rappresentazione del contenuto che dall’algoritmo viene esaminata.

Uso di space-before e space-after

Gli oggetti di dimensione diversa dall’altezza di riferimento devono essere preceduti e/o seguiti da spazi: si può allora pensare di indicare questi spazi direttamente nel documento fo, attraverso le proprietà space-before o space-after , eventualmente estendendone l’insieme dei possibili valori.

Spazi di dimensione fissa

Se dell’oggetto anomalo si conosce a priori con esattezza la dimensione (ad esempio se si tratta di un’immagine, o di un titolo che deve stare su una sola riga) basta calcolare la differenza fra l’altezza dell’oggetto e il più vicino multiplo dell’altezza di riferimento, e suddividerla a piacere fra spazio prima e spazio dopo.

Es.
[...]

In realtà, neppure questo semplice caso verrebbe sempre gestito correttamente: se l’oggetto si trova all’inizio o alla fine di una pagina le regole di line breaking fanno sì che uno degli spazi venga soppresso. Se ad essere scartato è lo spazio dopo l’oggetto, il risultato estetico è abbastanza buono: rispetto all’area della pagina in cui viene disposto il contenuto, l’oggetto ha comunque un uguale spazio prima e dopo; se però viene soppresso lo spazio prima, l’allineamento delle righe nella pagina viene irrimediabilmente compromesso. Per ovviare a questo problema, si potrebbe utilizzare la componente .conditionality degli spazi, attribuendole il valore retain .

Spazi di dimensione variabile

Se è un intero paragrafo ad essere scritto con un carattere più piccolo o più grande del normale bisogna trovare un modo per determinare, o per indicare, la dimensione degli spazi senza conoscere quante righe verranno effettivamente create.

Ovviamente è impossibile determinare a priori la dimensione esatta che gli spazi dovranno avere; utilizzando le componenti .minimum , .optimum e .maximum si può tuttavia indicare un range di variazione che consenta all’algoritmo di trovare una soluzione.

In realtà la componente .optimum non è utile, dato che in questa situazione la scelta della dimensione non dipende da criteri estetici (sui quali è l’utente ad avere l’ultima parola) bensì dalla necessità di riempire esattamente la pagina.

La differenza fra le componenti .minimum e .maximum deve essere pari all’altezza di riferimento; la componente .minimum può essere fissata a piacere per indicare la dimensione minima degli spazi di raccordo.

In questo modo se anche uno degli spazi viene soppresso, trovandosi all’inizio o alla fine di una pagina, l’altro è comunque sufficiente a raggiungere un multiplo dell’altezza di riferimento. Se invece nessuno spazio viene soppresso l’elasticità complessiva è pari al doppio dell’altezza di riferimento, e sembrerebbe quindi sovradimensionata; tuttavia difficilmente l’algoritmo la sfrutterà per più della metà, perchè potrebbe riempire ugualmente la pagina, e con costi minori, semplicemente aggiungendo una riga normale in più.

Alla rappresentazione dell’oggetto anomalo è quindi sufficiente aggiungere in testa e in coda un elemento glue( .minimum , .maximum - .minimum ,0).

Es.
supponiamo di avere una pagina alta 144 punti, da riempire con paragrafi “normali” con righe alte 12 punti (da mantenere allineate) e con alcuni paragrafi con righe alte 10 punti; in una pagina si devono disporre queste linee, con spazi di raccordo prima e dopo il blocchi anomalo (non è la sequenza di elementi, solo la successione delle altezze delle linee): 12 12 -spazio- 10 10 10 -spazio- 12 12 12 12 12 12 12 se ogni spazio può variare all’interno del range [0, 12], la differenza di 6 punti verrà ripartita in due parti uguali; la dimensione complessiva del paragrafo anomalo sarà 3*10+2*3 = 36 punti, che permette alle righe successive di essere correttamente allineate.

Es.
L’algoritmo avrebbe anche potuto sfruttare maggiormente l’elasticità degli spazi, inserendo una riga in meno nella pagina: 12 12 -spazio- 10 10 10 -spazio- 12 12 12 12 12 12 in questo caso, la differenza di 18 punti verrebbe gestita utilizzando spazi alti 9 punti.

Spazi percentuali

Se in una stessa pagina sono presenti più oggetti di dimensioni diverse dal normale, gli spazi così definiti non sono tuttavia sufficienti a mantenere l’allineamento delle righe.

Es.
12 12 -spazio- 10 10 10 -spazio- 12 12 12 -spazio- 10 10 -spazio- 12 12 se tutti gli spazi hanno le stesse dimensioni, l’algoritmo ripartirà la differenza di 10 punti in quattro parti uguali; così facendo il primo paragrafo anomalo diventerà alto complessivamente 3*10+2*2.5 = 35 punti, il secondo 2*10+2*2.5 = 25 punti, e le tre righe del blocco centrale non saranno correttamente allineate. La soluzione desiderata è invece avere uno spazio di 3 punti prima e dopo il primo paragrafo anomalo (per un totale di 36) e di 2 punti prima e dopo il secondo (in tutto 24), cioè spazi uguali, in tutti i casi, al 10% del paragrafo cui si riferiscono.

In questo caso, sembra sia sufficiente estendere FO in modo da poter assegnare un valore percentuale a space-before e space-after . Individuare la percentuale di spazio necessaria è semplice: basta calcolare, rispetto all’altezza di una riga anomala, l’aumento necessario per raggiungere il più vicino multiplo dell’altezza di riferimento.

Es.
12 12 -spazio1- 10 10 10 -spazio1- 12 12 12 -spazio2- 10 10 -spazio2- 12 12 per passare da 10 a 12 punti è necessario un aumento di 2 punti, cioè del 20%, che va diviso fra lo spazio prima e quello dopo: ogni spazio è pari al 10% del paragrafo; spazio1 è quindi rappresentato da un elemento glue(0,3,0) e spazio2 da glue(0,2,0); la differenza da gestire è 10 su un totale di 10, tutti gli spazi assumono la loro dimensione massima e le righe sono correttamente allineate.

I valori percentuali vengono convertiti in effettive lunghezze nel momento in cui, terminata l’esecuzione dell’algoritmo di line breaking, le dimensioni del paragrafo diventano note, e devono essere considerati dei valori massimi, con un minimo pari a 0. Se invece fossero considerati valori ottimali costanti, i paragrafi anomali composti da molte righe si troverebbero ad avere spazi di raccordo sovradimensionati.

Es.
se l’altezza di riferimento è 12 punti e gli spazi percentuali sono considerati costanti, un paragrafo con otto righe alte 10 punti sarebbe preceduto e seguito da spazi alti 8 punti, raggiungendo complessivamente una altezza pari a 96 punti; sarebbe però possibile mantenere l’allineamento utilizzando spazi più piccoli: con spazi alti 2 punti la dimensione complessiva sarebbe 84, che è ancora un multiplo dell’altezza di riferimento.

A questo punto ci si potrebbe chiedere perchè, dal momento che si conosce la dimensione complessiva del paragrafo, invece di calcolare un valore massimo non si calcola direttamente il valore preciso degli spazi: il punto è che conoscere l’effettiva altezza del paragrafo è sì necessario, ma non sufficiente per determinare l’ammontare degli spazi; questo dipende infatti anche dalla posizione del paragrafo all’interno della pagina, che potrebbe comportare la soppressione di uno degli spazi. Questa informazione non è nota durante la fase di rappresentazione dei paragrafi: lo sarà solo dopo aver raccolto la rappresentazione dell’intero contenuto della page-sequence e al termine dell’algoritmo di page breaking.

Tanto maggiore è la dimensione del paragrafo di dimensioni anomale, tanto maggiore può essere lo spazio di raccordo: così facendo però è possibile che l’algoritmo, utilizzando questa rappresentazione, oltre alle soluzioni giuste consideri delle soluzioni erronee, che non sono applicabili nella realtà. Ciò significa che questa rappresentazione non è sufficientemente precisa.

Es.
12 -spazio1- 8 8 8 8 8 -spazio1- 12 12 -spazio2- 8 8 -spazio2- 12 12 in questo caso la percentuale di spazio è del 25%, per cui spazio1 è rappresentato da glue(0,10,0) e spazio2 da glue(0,4,0); la differenza da colmare è 28 su uno stretch massimo di 28, quindi tutti gli spazi hanno la massima ampiezza e le righe sono allineate.

Es.
L’algoritmo avrebbe però potuto trovare una diversa soluzione, che invece di ricorrere al massimo allungamento possibile aggiunge una riga in più nella pagina: 12 -spazio1- 8 8 8 8 8 -spazio1- 12 12 -spazio2- 8 8 -spazio2- 12 12 12 il range di variazione degli spazi è lo stesso, ma ora la differenza è 16 su 28 (coefficiente 0.571); applicando questo coefficiente, spazio1 risulterebbe essere 5.71 punti e spazio2 2.26, e le righe centrali sarebbero disallineate; la soluzione giusta si avrebbe con spazio1 = 4 e spazio2 = 4.

Es.
Infine, l’algoritmo avrebbe potuto trovare una soluzione che utilizza il minimo stretch possibile: 12 -spazio1- 8 8 8 8 8 -spazio1- 12 12 -spazio2- 8 8 -spazio2- 12 12 12 12 la differenza è 4 su 28 (coefficiente 0.143); utilizzare il coefficiente porterebbe ad avere spazio1 = 1.43 e spazio2 = 0.57 che non mantengono allineate le righe; inoltre, a differenza di quanto accadeva nell’esempio precedente, non esiste una distribuzione di questa differenza negli spazi che mantenga l’allineamento delle righe, a meno di non porre spazio1 = -2 e spazio2 = 4.

Infine, così come accadeva con gli spazi di dimensioni fisse, anche questa rappresentazione non è in grado di gestire correttamente le situazioni in cui avvengono soppressioni di spazi.

Conclusioni

In definitiva:

  • per gli oggetti di dimensione fissa e nota a priori (immagini, titoli) è possibile definire gli spazi di raccordo utilizzando le proprietà space-before e space-after e assegnando loro valori fissi;

  • se in tutte le pagine è presente al più un solo oggetto di dimensioni anomale fisse ma ignote (prima di effettuare il line breaking) è possibile mantenere allineate le righe normali utilizzando le proprietà space-before e space-after , indicando opportunamente le loro componenti .minimum e .maximum ; questa rappresentazione funziona anche nel caso in cui uno degli spazi venga soppresso;

  • se però esiste almeno una pagina che contiene più di un oggetto anomalo di dimensioni fisse e ignote non è possibile garantire l’allineamento delle righe attraverso l’uso di proprietà esistenti, neppure definendo spazi in modo percentuale rispetto all’altezza del blocco cui si riferiscono;

  • a maggior ragione, queste tecniche non sono sufficienti per gestire correttamente paragrafi anomali con un numero di righe (e quindi una dimensione) variabile e ignoto.

Bisogna quindi necessariamente ricorrere ad una proprietà appositamente mirata al mantenimento dell’allineamento delle righe normali.

Necessità di una nuova proprietà

Gli spazi di raccordo hanno due importanti caratteristiche, che ne rendono impossibile una rappresentazione per mezzo delle proprietà space-before e space-after :

  • le dimensioni dei due spazi che precededono e seguono il paragrafo non sono sempre indipendenti l’una dall’altra;

  • al crescere del numero di righe, la dimensione degli spazi necessari non è costante né monotòna.

Conseguenze della dipendenza

Una volta stabilito il numero di righe, e supponendo che il paragrafo non venga interrotto da un page break, la somma degli spazi di raccordo è costante, indipendentemente dalla posizione nella pagina: se uno spazio viene soppresso, la sua altezza confluisce in quella dello spazio superstite.

Esiste cioè una sola grandezza, che non può quindi essere controllata mediante due diverse proprietà, né rappresentata utilizzando elementi distinti; proprio per questo motivo, nei precedenti tentativi non si riusciva a gestire correttamente le situazioni in cui avveniva la soppressione di uno spazio.

Conseguenze della non-monotonicità

I precedenti tentativi potevano portare all’individuazione di una soluzione in realtà non attuabile quando in una pagina comparivano più paragrafi anomali proprio perchè si tentava di approssimare una grandezza non monotona (la dimensione complessiva degli spazi) con una monotona (una quantità fissa, o una percentuale dell’altezza): si supponeva cioè che un paragrafo con più righe avesse sempre bisogno di spazi più ampi rispetto ad uno con meno righe, mentre ciò non è sempre vero.

Inoltre, la non monotonicità complica notevolmente la gestione dei punti di interruzione interni al paragrafo: se la dimensione complessiva degli spazi di raccordo fosse una funzione lineare (e quindi monotona) nel numero di righe sarebbe possibile determinarne il valore subito dopo aver effettuato il line breaking; in caso di suddivisione del paragrafo su più pagine, gli spazi verrebbero a loro volta ripartiti in modo proporzionale e il loro totale risulterebbe immutato.

Infatti, se la funzione che esprime la dimensione degli spazi di raccordo in relazione al numero di righe fosse della forma

spazi(n) = a * n e il paragrafo fosse diviso su due pagine

n = x + y => spazi(x) + spazi(y) = a * x + a * y n = x + y => spazi(x) + spazi(y) = a * (x + y) n = x + y => spazi(x) + spazi(y) = a * n n = x + y => spazi(x) + spazi(y) = spazi(n) Se il paragrafo anomalo viene disposto interamente in una pagina, come già detto la dimensione complessiva degli spazi di raccordo è costante ed indipendente dalla sua posizione e quindi dall’eventuale soppressione di uno spazio; se però il paragrafo anomalo viene suddiviso su due pagine la dimensione complessiva degli spazi potrebbe essere diversa, e comunque non è possibile ripartirla in parti uguali o proporzionali al numero di righe presenti su ogni pagina. A tutti gli effetti, un paragrafo interrotto si comporta come due distinti paragrafi, uno interamente in una pagina e l’altro in quella successiva.

La nuova proprietà

Date le ipotesi sotto cui si sta lavorando (in breve, presenza di paragrafi di dimensioni anomale che non devono turbare l’allineamento dei paragrafi “normali”) e le caratteristiche appena viste degli spazi di raccordo, si può pensare di indicare nel documento FO, come attributo dei paragrafi anomali, l’"unità di misura" di cui la dimensione delle aree generate dovrà essere un multiplo esatto.

In altre parole, si indica che la dimensione complessiva di ogni gruppo di righe anomale, comprendendo cioè sia le righe che gli spazi di raccordo, può variare solo in modo discreto, aggiungendo o togliendo una quantità pari al valore indicato nella proprietà, che potrebbe essere chiamata block-progression-unit .

Formalizzazione

A questo punto, è opportuno formalizzare e studiare in modo esatto la dimensione degli spazi di raccordo, prescindendo per il momento della rappresentazione in termini di box, glue e penalty (che è solo un possibile formalismo, con le sue regole e i suoi limiti).

All’altezza delle righe anomale deve essere aggiunta la minima quantità di spazi necessaria per raggiungere un multiplo dell’altezza di riferimento: la formula esatta è allora [...]

spazi(n) = top(n * han / hrif) * hrif - n * han dove hrif è l’altezza di riferimento, han l’altezza delle righe anomale e top la funzione di arrotondamento al più vicino intero maggiore o uguale.

Con un semplice raccoglimento a fattore comune, è possibile riscrivere la formula in modo da rendere più evidenti i possibili valori:

spazi(n) = hrif * (top(n * han / hrif) - (n * han / hrif)) In generale, la dimensione degli spazi varia all’interno dell’intervallo [0, hrif[; se poi han / hrif = m in N, cioè ogni riga anomala è alta esattamente quanto m righe normali, spazi(n) = 0 per ogni n. Quest’ultimo risultato, in particolare, è vero quando le due altezze sono uguali, cioè non sarebbe in realtà necessaria nessuna conversione: questa definizione degli spazi gode quindi della proprietà di idempotenza. [...]

Continuando a studiare questa funzione si può riscriverla in un modo diverso, evidenziandone l’andamento al variare del numero di righe:

n * han / hrif = (n * han - n * hrif + n * hrif) / hrif n * han / hrif = (n * hrif) / hrif + (n * han - n * hrif) / hrif n * han / hrif = n + n * (han - hrif) / hrif top(n * han / hrif) = top(n + n * (han - hrif) / hrif) top(n * han / hrif) = n + top(n * (han - hrif) / hrif) top(n * han / hrif) = n + variazione(n) in cui è stata definita la nuova funzione

variazione(n) = top(n * (han - hrif) / hrif) che è positiva e non decrescente se han > hrif, negativa e non crescente se invece han < hrif; tornando alla formula degli spazi, si ottiene:

spazi(n) = top(n * han / hrif) * hrif - n * han spazi(n) = (n + variazione(n)) * hrif - n * han spazi(n) = n * (hrif - han) + variazione(n) * hrif in cui si nota bene che oltre alla componente lineare ce n’è anche un’altra, che veniva completamente ignorata nel tentativo di rappresentazione per mezzo di spazi percentuali.

Complessivamente, un paragrafo anomalo composto da n righe avrà un’altezza pari a

altezza(n) = n * han + spazi(n) altezza(n) = n * han + n * (hrif - han) + variazione(n) * hrif altezza(n) = n * (han + hrif - han) + variazione(n) * hrif altezza(n) = n * hrif + variazione(n) * hrif altezza(n) = hrif * (n + variazione(n)) altezza(n) = hrif * unità(n) dove è stata definita la funzione di comodo

unità(n) = n + variazione(n) che calcola il numero complessivo di unità di riferimento necessarie alla rappresentazione di un paragrafo composto da n righe anomale. La funzione variazione(n) può quindi essere vista come la differenza fra il numero di righe anomale e il minimo numero di unità di riferimento necessarie per contenerle.

Periodicità degli spazi

La funzione che esprime la dimensione complessiva degli spazi di raccordo al variare del numero di righe del paragrafo ha sempre valori compresi nell’intervallo [0, hrif[; in particolare il suo andamento è periodico, e si può facilmente calcolare il periodo:

spazi(n) = spazi(n + k) n * (hrif - han) + variazione(n) * hrif = (n + k) * (hrif - han) + variazione(n + k) * hrif hrif * (variazione(n) - variazione(n + k)) = k * (hrif - han) variazione(n) - variazione(n + k) = k * (hrif - han) / hrif dal momento che variazione(m) in N per ogni m in N, k deve essere tale che

k * (hrif - han) / hrif in N e questa è l’unica condizione che k deve soddisfare; ipotizzando infatti che sia vera, si ha che

variazione(n + k) = top((n + k) * (han - hrif) / hrif) variazione(n + k) = top(n * (han - hrif) / hrif + k * (han - hrif) / hrif) variazione(n + k) = k * (han - hrif) / hrif + top(n * (han - hrif) / hrif) variazione(n) - variazione(n + k) = top(n * (han - hrif) / hrif) - (k * (han - hrif) / hrif + top(n * (han - hrif) / hrif)) = - k * (han - hrif) / hrif = k * (hrif - han) / hrif Determinare k, a questo punto, è banale; una volta definito il minimo comune multiplo

mc = mcm(hrif - han, hrif) si ha che

mc / (hrif - han) = a in N => (hrif - han) = mc / a mc / hrif = b in N => hrif = mc / b k * (hrif - han) / hrif = k * mc / a * b / mc k * (hrif - han) / hrif = k * b / a k * (hrif - han) / hrif in N <=> k = c * a, con c in N Il periodo della funzione spazi è quindi

T = a T = mc / (hrif - han) T = mcm(hrif - han, hrif) / (hrif - han) e corrisponde al minimo numero di righe necessarie affinchè la componente lineare degli spazi raggiunga esattamente le dimensioni di un multiplo dell’altezza di riferimento.

Un risultato interessante ottenuto nel corso della determinazione del periodo degli spazi, è una proprietà di regolarità della funzione variazione:

variazione(i) - variazione(i + T) = T * (hrif - han) / hrif Se hrif è un multiplo esatto di (hrif - han), la conoscenza del periodo della funzione spazi dà ulteriori informazioni sull’andamento della funzione variazione; con questa ipotesi aggiuntiva

T = mcm(hrif - han, hrif) / (hrif - han) T = hrif / (hrif - han) e questo risultato permette di esprimere la funzione variazione in un modo diverso:

variazione(n) = top(n * (han - hrif) / hrif) variazione(n) = top(- n * (hrif - han) / hrif) variazione(n) = - bottom(n / T) [...] In questo caso, è possibile definire con precisione gli insiemi di valori per i quali la funzione variazione è costante:

N = I0 unione I1 unione I2 unione ... Ii = {i * T, i * T + 1, ..., i * T + T- 1} variazione(n) = i per ogni n in Ii Es.: se hrif = 12 punti e han = 10 punti, (hrif - han) = 2 e mcm(2, 12) = 12 = hrif; il periodo della funzione spazi è 12 / 2 = 6, e la funzione variazione è costantemente i per ogni valore nell’insieme Ii = {i * 6, i * 6 + 1, ..., i * 6 + 5}

Interruzione dei paragrafi

Se un paragrafo composto da n righe di dimensioni anomale viene suddiviso su due pagine, ogni gruppo di righe avrà bisogno di uno spazio di raccordo:

n = i + (n - i) => spazi(i) = i * (hrif - han) + variazione(i) * hrif => spazi(n - i) = (n - i) * (hrif - han) + variazione(n - i) * hrif la dimensione complessiva degli spazi, dividendo il paragrafo, risulta quindi essere:

spazi(i) + spazi(n - i) = n * (hrif - han) + (variazione(i) + variazione(n - i)) * hrif >= n * (hrif - han) + variazione(n) * hrif = spazi(n) a causa degli arrotondamenti per eccesso nella funzione variazione; più precisamente, vale la disuguaglianza

variazione(n) <= variazione(i) + variazione(n - i) <= variazione(n) + 1 in cui, essendo variazione(m) in N per ogni m, al variare di i uno dei simboli di “minore o uguale” vale “minore” e l’altro “uguale”; si può quindi scrivere questa relazione in forma di uguaglianza:

variazione(i) + variazione(n - i) = variazione(n) + d con d in {0, 1}; il valore di d dipende da quello della nuova proprietà, da n e da i.

Infatti, indicando con int(x) e dec(x) rispettivamente la parte intera e la parte decimale di x in R, si ha che

a = int(a) + dec(a) b = int(b) + dec(b) top(a) = top(int(a) + dec(a)) top(a) = int(a) + top(dec(a)) top(b) = int(b) + top(dec(b)) top(a + b) = top (int(a) + dec(a) + int(b) + dec(b)) top(a + b) = int(a) + int(b) + top(dec(a) + dec(b)) se dec(a) = dec(b) = 0 > top(dec(a)) = top(dec(b)) = top(dec(a) + dec(b)) = 0 => top(a) + top(b) = top(a + b) se dec(a) = 0 e dec(b) > 0 => top(dec(a)) = 0 => top(dec(b)) = top(dec(a) + dec(b)) = 1 => top(a) + top(b) = top(a + b) se dec(a) > 0, dec(b) > 0 e dec(a) + dec(b) < 1 => top(dec(a)) = top(dec(b) = 1 => top(dec(a) + dec(b)) = 1 < top(dec(a)) + top(dec(b)) = 2 => top(a) + top(b) = top(a + b) + 1 se dec(a) > 0, dec(b) > 0 e dec(a) + dec(b) > 1 => top(dec(a)) = top(dec(b) = 1 => top(dec(a) + dec(b)) = 2 = top(dec(a)) + top(dec(b) => top(a) + top(b) = top(a + b) [...]

Questo significa che dividere un paragrafo anomalo può comportare un aumento delle sue dimensioni rispetto alla sua disposizione su una sola pagina. In particolare, se si considera la presenza di un solo break all’interno del paragrafo l’eventuale aumento delle dimensioni, che potrebbe verificarsi in corrispondenza di alcune posizioni di interruzione e non in altre, è pari a una unità di riferimento. [...]

Non bisogna tuttavia confondere questo fenomeno con quanto visto in precedenza a proposito della rappresentazione di un paragrafo con più possibilità di layout; in questo caso si sta infatti considerando, per il momento, una sola disposizione del paragrafo, e un’eventuale interruzione non comporta un aumento nel numero di righe, ma solo negli spazi di raccordo.

Possibili approcci

La proprietà block-progression-unit indica di quale valore l’altezza delle aree generate dal formatting object per cui è definita deve essere un multiplo esatto.

Nella rappresentazione di questi formatting object tutti gli elementi box, penalty e glue devono quindi avere come dimensione un multiplo di questa unità di riferimento; le ipotesi valide nel contesto di utilizzo di questa proprietà fanno sì che i break calcolati a partire da questa rappresentazione comportino sempre modifiche intere al numero di righe occupate da ogni paragrafo. La complessità del problema si sposta quindi dalla fase della sua definizione nel documento FO attraverso le proprietà (ora divenuta un’operazione banale, grazie alla proprietà appositamente introdotta) alla fase di rappresentazione interna al programma.

Si potrebbe pensare di costruire direttamente una sequenza con queste caratteristiche, utilizzando i parametri min , opt , max , orf e ved . Questa scelta non consentirebbe tuttavia di definire la proprietà block-progression-unit a un livello superiore rispetto a quello dei blocchi di testo, cioè di avere più paragrafi anomali (magari ognuno con una diversa dimensione del carattere) raggruppati insieme in modo da avere spazi di raccordo solo prima del primo e dopo l’ultimo, e non anche tra di loro.

Es.
è piuttosto comune avere titoli preceduti da un occhiello e seguiti da un sottotitolo, e ognuno di questi elementi ha un’altezza diversa rispetto a quella del testo seguente; [...]

Meglio allora ottenere prima la sequenza di elementi box, penalty e glue corrispondente al contenuto di dimensioni anomale, senza tenere conto della proprietà, e utilizzarla per creare quella nuova. Questa sequenza verrà poi esaminata dall’algoritmo di page breaking, e sarà quindi necessario stabilire un mapping tra i punti di interruzione corrispondenti, per poter interpretare i page break calcolati e stabilire il numero di righe e i corrispondenti spazi di raccordo da disporre in ogni pagina.

Situazioni possibili

Per come si è scelto di procedere, non è possibile fare una classificazione dei casi possibili che si basi sui parametri min , opt , max , orf e ved , dato che la sequenza da convertire potrebbe rappresentare più di un paragrafo.

Non è neppure possibile esaminare caso per caso le varie tipologie di righe, ricercando una nuova sottosequenza di elementi corrispondente a una generica riga di ogni diversa classe, così come era stato fatto in precedenza per i paragrafi con un numero variabile di righe; [...]

Invece di considerare una serie di alternative possibili, si può allora procedere in modo incrementale, iniziando dal caso più semplice e procedendo verso situazioni via via più generali:

  1. la sequenza originale rappresenta solo righe iniziali, interne e finali;
  2. sono rappresentate anche righe opzionali ed eliminabili;
  3. sono rappresentate anche righe condizionate.

In tutti questi casi, si procederà esaminando la sequenza originale e fermandosi ad ogni suo possibile punto di interruzione, calcolando le dimensioni parziali del paragrafo diviso e generando, in rapporto alle dimensioni totali, i nuovi elementi.

Convenzioni e parametri

Per costruzione, i punti di interruzione della sequenza originale sono rappresentati sempre e solo da elementi penalty di valore non infinito; nella sequenza, infatti, non sono mai presenti elementi glue immediatamente preceduti da un elemento box.

Se la sequenza originale è

sTOT = elemento1 ... elementok-1 penalty() elementok+1 ... elementon l’utilizzo del punto di interruzione rappresentato dall’elemento penalty definisce le due sottosequenze

sSX = elemento1 ... elementok-1 penalty() sDX = elementok+h elementon in cui i primi (h - 1) elementi immediatamente dopo la penalty vengono ignorati per effetto della regole di soppressione.

Di tutte queste sequenze (quella intera e le due sottosequenze) è possibile calcolare la dimensione minima, ottimale e massima: tali dimensioni sono lunghezze effettive, non quantità di righe, quindi per evitare confusioni con i nomi usati in precedenza verranno indicate con lMin , lOpt e lMax .

Nel caso in cui fosse necessario fare confronti con le dimensioni delle sottosequenze generate da un punto di interruzione precedente, queste verranno indicate con prevMin , prevOpt e prevMax .

Tutte queste lunghezze non possono essere usate come parametri della funzione unità(n), che per come è definita attualmente calcola il numero di unità di riferimento necessarie a contenere n righe alte han; per comodità, ma anche per considerare il caso più generale in cui non tutte le righe anomale sono ugualmente alte, è quindi necessario definire la funzione:

unitàL(l) = top(l / hrif) La nuova sequenza dovrà essere tale che:

  • la lunghezza complessiva, espressa come quantità di unità di riferimento, deve essere unitàL(lOptTOT), la stretchability complessiva unitàL(lMaxTOT) - unitàL(lOptTOT) e la shrinkability complessiva unitàL(lOptTOT) - unitàL(lMinTOT);

  • i punti di interruzione della nuova sequenza devono rappresentare tutte e sole le possibilità di dividere il contenuto rappresentato dalla sequenza originale, tenendo conto degli spazi di raccordo per ciascuna delle due porzioni; per ogni possibilità di dividere le n righe in x + y, deve esserci un punto di interruzione della nuova sequenza che consente di disporre unità(x) unità di riferimento in una pagina e unità(y) in quella seguente.

Durante la fase di conversione, è necessario tenere il conto delle dimensioni parziali della nuova sequenza che si sta costruendo, espresse come quantità di unità di riferimento: suddividendole fra unità normali, unità di stretch e unità di shrink, verranno rispettivamente indicate con uOpt , uStretch e uShrink .

Situazione 1

Le righe iniziali, interne e finali generano solamente elementi di dimensione fissa: in assenza di righe di classi diverse è quindi rappresentato un solo possibile layout. Per la sequenza originale e per ogni possibile sottosequenza si ha che lMin = lOpt = lMax; anche i nuovi elementi, di cui bisogna poi stabilire le dimensioni, saranno tutti di dimensione fissa:

  • per ogni punto di interruzione basterà creare una coppia box + penalty;

  • all’ultimo frammento della sequenza originale corrisponederà solo un elemento box.

La sequenza originale gode della proprietà di conservazione della lunghezza: in corrispondenza di ogni punto di interruzione la somma delle lunghezze delle due sottosequenze è pari alla lunghezza complessiva della sequenza, cioè vale

Dlopt = lOptSX + lOptDX - lOptTOT Dlopt = 0 Questa proprietà può non essere vera nel momento in cui si passa dal numero di righe al numero di unità di riferimento necessarie a contenerle, a causa dell’operazione di arrotondamento: in corrispondenza di alcuni dei punti di interruzione interni alla sequenza, la lunghezza complessiva potrebbe infatti essere superiore di una unità rispetto alla lunghezza del paragrafo ininterrotto:

Duopt = unitàL(lOptSX) + unitàL(lOptDX) - unitàL(lOptTOT) Duopt = d con d in {0, 1} e dipendente dal punto di interruzione.

Le variazioni di lunghezza devono essere individuate e gestite localmente al punto di interruzione, cioè utilizzando elementi che abbiano effetto solo nel caso in cui lo specifico punto di interruzione venga utilizzato.

I nuovi elementi dovranno aggiungere un numero di unità pari a unitàL(lOptSX) - uOpt, che verranno ripartire a seconda del valore di Duopt: se vale 1, una unità verrà rappresentata utilizzando la lunghezza dell’elemento penalty; le restanti unità saranno la dimensione dell’elemento box e andranno ad incrementare il valore di uOpt. L’eventuale lunghezza dell’elemento penalty non viene usata per modificare uOpt proprio perchè agisce solo in modo locale: se viene utilizzato un diverso punto di interruzione non ha nessun effetto.

Un altro fenomeno che si può incontrare anche in questo caso semplice è quello della sovrapposizione di soluzioni distinte: punti di interruzione diversi nella sequenza originale potrebbero definire sottosequenze che necessitano di uno stesso numero di unità, sia prima che dopo il break. La condizione che identifica queste situazioni è

prevOptSX = lOptSX AND prevOptDX = lOptDX [...]

Es.
per un paragrafo composto da 11 righe alte 10 punti, con block-progression-unit = 12 punti, le possibili interruzioni 5+6 e 6+5 portano ad occupare 5 unità in una pagina e 5 in quella successiva.

Dal punto di vista della creazione della nuova sequenza, questo non è un problema: semplicemente non è necessario aggiungere alcun elemento alla sequenza in costruzione, dato che quelli esistenti rappresentano già la nuova suddivisione del paragrafo. Questo fenomeno riguarda quindi solo il mapping tra interruzioni della nuova e della vecchia sequenza: bisogna scegliere, fra le soluzioni coincidenti, quella che dovrà essere applicata in caso di utilizzo del punto di interruzione.

[...]

[...]

Situazione 2

Le righe opzionali e quelle eliminabili vengono rappresentate utilizzando anche elementi di dimensione variabile: la loro presenza indica l’esistenza di più possibili disposizioni del contenuto in righe.

Nella sequenza originale le quantità complessive di stretch e di shrink si conservano, perchè nessun elemento glue può venir mai soppresso: per ogni punto di interruzione vale

Dlstretch = (lMaxSX - lOptSX) + (lMaxDX - lOptDX) - (lMaxTOT - lOptTOT) Dlstretch = 0 Dlshrink = (lOptSX - lMinSX) + (lOptDX - lMinDX) - (lOptTOT - lMinTOT) Dlshrink = 0 Passando al numero di unità, queste uguaglianze non sono sempre valide; analizzando ad esempio le unità di stretch (ma lo stesso discorso vale anche per lo shrink) si ottiene infatti

Dustretch = (unitàL(lMaxSX) - unitàL(lOptSX)) Dustretch = + (unitàL(lMaxDX) - unitàL(lOptDX)) Dustretch = - (unitàL(lMaxTOT) - unitàL(lOptTOT)) Dustretch = (unitàL(lMaxSX) + unitàL(lMaxDX) - unitàL(lMaxTOT)) Dustretch = - (unitàL(lOptSX) + unitàL(lOptDX) - unitàL(lOptTOT)) Dustretch = d1 - d2 Nel passaggio finale è stata usata la formula già dimostrata in precedenza: a seconda del punto di interruzione d1 e d2 possono valere 0 o 1, quindi la differenza locale rispetto alle unità di stretch dell’intera nuova sequenza può essere 1, 0 oppure - 1. [...]

[...]

Trovare una combinazione di elementi che possa variare le dimensioni localmente senza influire sul totale non è difficile: basta rappresentare il punto di interruzione con la sottosequenza penalty(0, inf ) glue(0, x, y) penalty(z, 0) glue(0, -x, -y) anzichè con penalty(z, 0); una soluzione di questo tipo è già stata usata per rappresentare le righe condizionate. Questi elementi hanno effetto, in caso di utilizzo della penalty, solo sulla sottosequenza di sinistra: è a questa che possono aggiungere o togliere stretch e shrink a seconda dei segni di x e y, mentre, a causa dell’asimmetria della regola di soppressione, le dimensioni della sottosequenza destra non vengono modificate.

È da notare che le differenze locali possono avvenire in corrispondenza di un qualunque punto di interruzione della sequenza originale, non necessariamente in quelli adiacenti alle linee opzionali: affinchè le variazioni locali agiscano sempre correttamente, è quindi necessario che all’interno della sequenza originale la rappresentazione delle righe opzionali ed eliminabili sia nella parte iniziale, immediatamente dopo alla rappresentazione delle prime orf righe. In caso contrario, infatti, una variazione negativa (con x = -1) che avvenisse per un break precedente le righe opzionali avrebbe l’effetto di sottrarre una unità di stretch a sinistra, portando ad un assurdo totale negativo e soprattutto lasciando immutata la sottosequenza destra con tutte le sue unità di stretch.

Con una sequenza originale così ordinata, rimane solo un punto di interruzione problematico: il primo, che segue le prime orf righe e precede tutte le altre, comprese quelle opzionali. In corrispondenza di questo punto ci potrebbe essere una differenza Dustretch = -1, che può essere gestita correttamente semplicemente “anticipando” una unità di stretch, che verrà annullata dalla differenza locale; questa operazione è sempre possibile, dato che in assenza di stretch non potrebbe esserci una vaziazione locale. [...]

In corrispondenza di ogni punto di interruzione, dovranno essere aggiunti nella nuova sequenza gli elementi per poter gestire un numero di unità normali, di stretch e di shrink rispettivamente pari a:

nuoveOpt = unitàL(lOptSX) - uOpt; nuoveStretch = (unitàL(lMaxSX) - unitàL(lOptSX)) - uStretch nuoveShrink = (unitàL(lOptSX) - unitàL(lMinSX)) - uShrink Per quanto riguarda la gestione delle unità normali, ci si comporta esattamente come nella situazione precedente, suddividendole tra lunghezza del box e della penalty.

Le nuoveStretch unità di stretch vanno suddivise tra quelle che rappresentano una variazione locale e quelle che invece fanno parte del totale: le prime vengono calcolate utilizzando la formula vista in precedenza, e vengono rappresentate utilizzando gli elementi glue che precedono e seguono la penalty; le rimanenti verranno rappresentate con un elemento glue mai sopprimibile e andranno ad incrementare uStretch. La gestione delle unità di shrink è del tutto identica. [...]

[...]

Situazione 3

Anche le righe opzionali condizionate e quelle eliminabili condizionate originano elementi di dimensione variabile, che però hanno effetto solo se il paragrafo non viene diviso: essendo del tutto ininfluenti in caso di interruzione del paragrafo non sono soggette a differenze locali nel numero di unità, e non aggiungono quindi particolari difficoltà.

A differenza del caso precedente, ora nella sequenza originale c’è una differenza tra la quantità di stretch (o shrink) dell’intera sequenza e quella delle due sottosequenze originate da un break, e questa differenza è costante:

Dlstretch = (lMaxSX - lOptSX) + (lMaxDX - lOptDX) - (lMaxTOT - lOptTOT) Dlstretch = (lMaxSX + lMaxDX - lMaxTOT) - (lOptSX + lOptDX - lOptTOT) Dlstretch = 0 - (lOptTOT + c - lOptTOT) Dlstretch = - c Passando dalle lunghezze al numero di unità si ottiene

Dustretch = (unitàL(lMaxSX) - unitàL(lOptSX)) Dustretch = + (unitàL(lMaxDX) - unitàL(lOptDX)) Dustretch = - (unitàL(lMaxTOT) - unitàL(lOptTOT)) Dustretch = (unitàL(lMaxSX) + unitàL(lMaxDX) - unitàL(lMaxTOT)) Dustretch = - (unitàL(lOptSX) + unitàL(lOptDX) - unitàL(lOptTOT)) Dustretch = d1 - (unitàL(lOptTOT + c) + d2 - unitàL(lOptTOT)) Dustretch = d1 - (unitàL(lOptTOT) + unitàL(c) - d3 + d2 - unitàL(lOptTOT)) Dustretch = - unitàL(c) + d1 - d2 + d3 Dustretch in {- unitàL(c) - 1, ..., - unitàL(c) + 2} La gestione delle righe eliminabili condizionate avviene in modo del tutto analogo. [...]

Più paragrafi

Invece di un singolo paragrafo, si possono avere più paragrafi che, nel loro complesso, devono occupare un multiplo dell’altezza di riferimento: si desidera cioè due soli spazi di raccordo all'inizio e alla fine, e non anche in mezzo ai vari paragrafi.

Questa nuova situazione, in teoria più generale della precedente, non è in realtà più complessa. [...]

Procedura di conversione

Il fenomeno dei possibili aumenti di dimensione a seconda del punto di interruzione utilizzato impone di utilizzare una procedura di conversione a due fasi:

  1. si esamina la vecchia sequenza una prima volta, dall’inizio alla fine, per calcolare le dimensioni (lunghezza, stretch e shrink) complessive del paragrafo nell’ipotesi in cui non venga interrotto
  2. si riesamina la vecchia sequenza dall’inizio, fermandosi in corrispondenza di ogni possibile punto di interruzione per calcolare le dimensioni parziali di entrambe le sotto-sequenze e generare parte della nuova sequenza

[...]

Spazi di raccordo minimi e condizionali

Dal momento che si è utilizzata una nuova proprietà per definire gli spazi di raccordo per i paragrafi di dimensioni anomale, bisogna tenere in considerazione eventuali “interferenze” con proprietà già esistenti: in particolare, bisogna stabilire il comportamento da tenere se nel blocco per cui è definita block-progression-unit , o in un blocco al suo interno, sono definite anche le proprietà space-before e space-after , con le loro diverse componenti.

Un solo paragrafo

Utilizzando block-progression-unit , la dimensione di uno spazio di raccordo può variare nel range [0, hrif / 2[ nel caso in cui il paragrafo sia tutto in una pagina, non all’inizio e non alla fine, oppure nel range [0, hrif[ nel caso in cui sia diviso o si trovi all’inizio o alla fine di una pagina.

Per un singolo paragrafo di questo tipo non è sensato definire spazi prima o dopo, di lunghezza fissa o in un range, dato che questi potrebbero alterare l’allineamento delle righe; è però sensato pensare di indicare uno spazio minimo, in modo da evitare che questi paragrafi speciali appaiano troppo attaccati a quelli che li precedono o seguono.

Garantire il rispetto di questi spazi minimi non è difficile se la loro componente .conditionality vale “retain”, cioè se questi spazi devono essere considerati anche se immediatamente seguiti o preceduti da un’interruzione di pagina.

Es.
una situazione di questo genere si può avere nel caso di titoli a inizio pagina, preceduti e seguiti da spazi (come i titoli dei capitoli di questa tesi): non potendo sapere se il titolo occuperà una o più righe, non è possibile fissare a priori la dimensione degli spazi; si può quindi pensare di utilizzare la proprietà block-progression-unit per non turbare l’allineamento delle righe, in combinazione con space-before e space-after per indicare i valori minimi desiderati, cioè quelli relativi ad una sola riga.

In questo caso basta aggiungere all’inizio e alla fine della sequenza originale, che codifica le righe, due elementi box che rappresentino gli spazi minimi, ottenendo:

box(spazioPrima) box(w1) .... box(wn) box(spazioDopo) con

spazioPrima = space-before.minimum spazioDopo = space-after.minimum e utilizzando poi la procedura di conversione già vista.

Così facendo, in caso di interruzione del paragrafo lo spazio di raccordo iniziale varierà nell’intervallo [spazioPrima, spazioPrima + hrif[ e quello finale in [spazioDopo, spazioDopo + hrif[; se invece il paragrafo rimane intero lo spazio iniziale varierà in [spazioPrima, spazioPrima + hrif / 2[ e quello finale in [spazioDopo, spazioDopo + hrif / 2[.

La situazione è un po’ più complessa se .conditionality vale “discard”, cioè gli spazi devono essere ignorati qualora si trovassero all’inizio o alla fine della pagina. È da notare che l’interruzione del paragrafo implica la presenza di entrambi gli spazi di raccordo, mentre se il paragrafo rimane intero uno dei due spazi potrebbe essere da ignorare, ma mai entrambi: le difficoltà sorgono quindi nel caso in cui il paragrafo sia tutto in una stessa pagina.

Se sono presenti entrambi gli spazi di raccordo, la dimensione complessiva ottimale della nuova sequenza deve essere

unitàL(spazioPrima + lOptTOT + spazioDopo) se lo spazio iniziale viene ignorato, la dimensione dovrà essere

unitàL(lOptTOT + spazioDopo) se invece viene ignorato quello finale, si avrà

unitàL(spazioPrima + lOptTOT) si possono quindi definire le variazioni di lunghezza che avvengono nei due casi:

Duprima = unitàL(spazioPrima + lOptTOT + spazioDopo) Duprima = - unitàL(lOptTOT + spazioDopo) Duprima = unitàL(spazioPrima) + unitàL(lOptTOT + spazioDopo) - d1 Duprima = - unitàL(lOptTOT + spazioDopo) Duprima = unitàL(spazioPrima) - d1 Dudopo = unitàL(spazioPrima + lOptTOT + spazioDopo) Dudopo = - unitàL(spazioPrima + lOptTOT) Dudopo = unitàL(spazioDopo) - d2 con d1, d2 in {0, 1}.

Come punto di partenza di può allora utilizzare la nuova sequenza che si ottiene ipotizzando che gli spazi non siano condizionali (cioè aggiungendo i due elementi box prima della conversione): se entrambe le variazioni sono nulle, la rappresentazione del paragrafo non ha bisogno di modifiche; altrimenti, bisogna modificare il primo e l’ultimo elemento box in modo da passare da

box(x1) ... box(xn) a

glue(Duprima, 0, 0) box(x1-Duprima) ... box(xn-Dudopo) glue(Dudopo, 0, 0) In questo modo non si modifica la lunghezza complessiva della sequenza, a meno che uno degli elementi glue esterni non venga soppresso a causa di un break immediatamente precedente o successivo al paragrafo.

Questa modifica è sempre applicabile, dato che nel determinare la dimensione del primo box si è tenuto conto dello spazio minimo iniziale, e analogamente per la dimensione dell’ultimo è stato preso in considerazione quello finale, grazie agli elementi aggiunti alla sequenza originale.

Più paragrafi

Se invece ci sono più paragrafi racchiusi da un unico blocco in cui è usata la nuova proprietà, ad interferire direttamente con gli spazi di raccordo sono solo lo spazio precedente il primo paragrafo, quello seguente l’ultimo e quelli definiti nel blocco esterno che contiene tutto: questi verranno gestiti con nel caso precedente, considerandone solo la dimensione minima e modificando quindi il range di variazione; gli spazi all’interno della sequenza originale influiscono invece in modo indiretto, agendo sulla dimensione del contenuto.

Se gli spazi interni non sono condizionali la procedura di conversione usata fin’ora è già in grado di generare una sequenza corretta. La presenza di spazi condizionali introduce invece due nuovi fattori da considerare:

  • i punti di interruzione della sequenza originale non sono più solo elementi penalty con valore non infinito, ma possono essere anche elementi glue;

  • la dimensione complessiva dei paragrafi rappresentati può variare, oltre che in modo discreto aumentando o diminuendo le righe, anche in modo continuo modificando gli spazi all’interno del loro range di variazione.

. [...]

---++ Lunghezza delle righe variabile

Solitamente, giustificare orizzontalmente un paragrafo significa suddividerlo in porzioni che contengano elementi di dimensione variabile, quali ad esempio spazi fra parole e spazi fra lettere, così che ogni porzione possa essere espansa o contratta per riempire esattamente lo spazio a disposizione, ottenendo righe della desiderata larghezza.

Si potrebbe però pensare di fare il contrario, cioè aumentare o diminuire l’ampiezza delle righe per adattarle al loro contenuto. Ovviamente la scelta della larghezza non può essere fatta singolarmente per ogni riga, dato che ciò significherebbe non giustificare affatto; un approccio di questo tipo ha senso solo se la scelta viene fatta nell’ambito di una pagina, e ha effetto su tutte le righe contenute.

Il risultato che si vuole ottenere è una riduzione delle variazioni da apportare ad ogni riga: piccole variazioni nei margini della pagina, difficilmente percettibili, potrebbero evitare la presenza di righe con spazi molto ampi, che invece saltano subito agli occhi.

Ottimizzazione della lunghezza

[...] .

Vecchi e nuovi break

Si è appena visto come riutilizzare i break già individuati, semplicemente ricalcolando le variazioni da apportare ad ogni riga; le modifiche della lunghezza a disposizione possono però essere usate molto prima, nella fase di rappresentazione del problema, per individuare nuove possibilità di disporre i paragrafi in righe. Nella procedura di line breaking il parametro più influente, come si può facilmente immaginare, è proprio la larghezza delle righe: diminuendola o aumentandola di una piccola percentuale si possono talvolta ottenere disposizioni con, rispettivamente, un numero maggiore o minore di linee.

È da notare che non sempre questo avviene: se tutte le righe calcolate in base ad una certa larghezza necessitano di una variazione di segno positivo, cioè tutte devono essere allargate, è assai probabile che richiamando l’algoritmo di line breaking con una larghezza leggermente inferiore si ottengano esattamente gli stessi break; dualmente, se tutte le variazioni sono negative i break calcolati per righe più lunghe non saranno diversi. Anche nel caso in cui siano presenti variazioni di segno diverso, la posizione di alcuni break potrebbe essere quasi obbligata, ad esempio a causa di proprietà keep o break, o di parole particolarmente lunghe, e la ripetizione del line breaking potrebbe non portare nessuna nuova soluzione.

Non si può quindi pensare di chiamare la procedura di line breaking più volte per ogni paragrafo, dal momento che il maggior costo potrebbe non essere ripagato da migliori risultati: si potrebbe però fare qualche tentativo in più per quei paragrafi per i quali la prima esecuzione dell’algoritmo ha trovato un solo possibile layout, e che potrebbero quindi creare difficoltà al momento di fare la suddivisione in pagine.

L’algoritmo di page breaking

.

Riepilogo delle estensioni a XML-FO

...

Implementazione

Programmi esistenti

Attualmente esistono diversi formatter FO, cioè programmi che prendono in input un file FO, o un file XML insieme ad un foglio di stile XSLT, e producono un output che rispetti le proprietà indicate, espresso in un formato facilmente visualizzabile, portabile e stampabile, ad esempio PDF o PS.

Programmi open source

Il principale programma open source in questo ambito è FOP (Formatting Object Processor), che fa parte del progetto Apache e si propone di [...] .

L’ultima release ufficiale, è la versione 0.20.5; questa versione, detta “maintenance branch”, è però stata congelata verso la fine del 2001, dato che la sua architettura non avrebbe permesso di raggiungere il livello di conformità alle specifiche desiderato, e su di essa non vengono più fatte o considerate modifiche.

La versione attualmente in sviluppo è nata da una completa reingegnerizzazione; le sue funzionalità sono in continua evoluzione e i tempi in cui sarà sufficientemente matura per una release ufficiale si avvicinano, anche se, come del resto accade per la maggior parte del mondo open source, non ci sono scadenze precise.

È questa versione sperimentale, date le sue maggiori potenzialità rispetto a quella ferma, ad essere stata scelta come punto di partenza per implementare le estensioni che sono state analizzate nei capitoli precedenti.

[...]

Programmi proprietari

...

Ovviamente, a causa delle licenze sotto i cui termini vengono distribuiti questi programmi, non è possibile studiarne il funzionamento nè tanto mento modificarli per implementare le nuove proprietà; la loro utilità è quindi limitata al fornire degli output di confronto.

Fasi dell’elaborazione

Il processo di elaborazione implementato da FOP è molto simile alla procedura concettuale definita in modo astratto nelle specifiche di XSL FO. Supponendo di avere già a disposizione un file FO, altrimenti viene utilizzato Xalan per effettuare la trasformazione XSLT, si hanno tre fasi successive:

  1. oggettizzazione;
  2. layout;
  3. rendering.

Nella fase iniziale viene effettuato il parsing dell’input, durante il quale si verifica la correttezza sintattica (corretto bilanciamento dei tag) e semantica (conformità del contenuto di ogni elemento al suo content model) del file FO; man mano che l’analisi procede, viene creata la rappresentazione interna dell’input, l’albero dei formatting object. Questa fase comprende anche il “raffinamento”: vengono valutate le espressioni, effettuate le conversioni tra unità di misura diverse e propagati i valori ereditati, in modo da determinare il preciso valore delle proprietà per ogni elemento.

Durante la fase di layout le informazioni contenute nell’albero dei formatting object vengono utilizzate per creare l’albero delle aree, cioè una rappresentazione generica delle pagine in cui verrà disposto il contenuto e della disposizione del testo e degli altri oggetti all’interno di esse. Il funzionamento di questa fase verrà discusso più in dettaglio fra breve.

Infine, nella fase di rendering le informazioni contenute nell’albero delle aree vengono tradotte in un formato specifico per generare il documento di output.

La fase di layout

.

L’albero dei layout manager

. [...]

Ogni layout manager è responsabile di un nodo dell’albero dei formatting objects, e avrà quindi caratteristiche e comportamenti specifici a seconda dell’elemento che deve gestire. Partendo quindi dai nodi a più alto livello (intendendo quelli corrispondenti agli elementi più esterni, che contengono gli altri), ci sarà un PageSequenceLayoutManager? per ogni elemento fo:page-sequence , un FlowLayoutManager? per ogni fo:flow , un BlockLayoutManager? per ogni fo:block , e così via per tutti gli elementi a livello di blocco e di inline; un nodo di testo, ad esempio il contenuto di un fo:block che non sia un altro elemento, è controllato da un TextLayoutManager?.

Questa corrispondenza tra albero dei formatting object e albero dei layout manager ha poche eccezioni, la più significativa delle quali riguarda i nodi figli degli elementi fo:block e i relativi layout manager: [...]

Interazione fra layout manager

L’interazione fra i diversi layout manager, così come suggerito dalla loro organizzazione, avviene in modo ricorsivo, in un ordine equivalente ad una visita in profondità (depth first) dell’albero dei layout manager. [...]

. [...]

Strategia di layout prima delle modifiche

...

Strategia di layout dopo le modifiche

...

Conclusioni

Problemi rimasti aperti

...


to top

I Attachment sort Action Size Date Who Comment
versione.pdf manage 151.1 K 16 Feb 2005 - 10:38 LucaFurini Versione pdf del contenuto del wiki, con formule piu' leggibili

You are here: Tesi > ArgomentiDiTesi > EstensioniDiXSLFO

to top

Copyright © 1999-2017 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Fabio's Wiki? Send feedback