Stromové pakování (TreePacking)8.11.2025
Rychlý algoritmus řešící plnění kontejnerů položkami různé velikosti, zde 1D varianta vycházející z požadavku na minimalistické naplnění dokumentů LibreOffice různě dlouhými bloky textu tak, aby byl ušetřen papír při tisku viz. poradna LibreOffice. 3D varianta např. pro automatické plnění kamiónů či nákupních košíků je jen nakousnuta
.
V případě tohoto algoritmu není nalezeno zcela optimální naplnění kontejnerů, ale je extra urychleno základní plnění kdy pokud se položka vejde tak je prostě zařazena a není nijak dále propočítáváno jestli třeba jinam či v jiné kombinaci by se mohla vejít lépe. Položky nejsou procházeny jedna po druhé a není žádné opakované tupé porovnávání vzájemné velikosti položek či míst v kontejnerech, ale je rovnou vybrána největší možná.
Pro dokument jsou brány tři druhy textových bloků, velké bloky které přesahují stránku, poloviční bloky delší než polovina stránky ale kratší než stránka, a krátké bloky kratší než polovina stránky.
A co když blok zabírá přesně polovinu stránky? Pak záleží na tom jestli mezi jednotlivými bloky je potřeba nějaký oddělovač (např. prázdný řádek) nebo jestli jsou vkládané čistě za sebe. Jde o to, že poloviční bloky by se neměly vejít dva na stránku.
Když jsem tento algoritmus uváděl do praxe, mezi bloky byla dávána čára z mínusů jako oddělovač, tudíž poté jako poloviční bloky byly brány ty, které měly větší velikost než byla velikost stránky snížená o velikost oddělovače a vydělená dvěmi = (výškaStránky – výškaOddělovače)/2, takže poloviční blok mohl být i o něco menší než polovina stránky.
Dále je potřeba rozhodnout jakým způsobem budou které bloky vypisovány, já praktikoval variantu kdy byly vypsané nejprve všechny dlouhé bloky, zbytek stránky po nich byl vyplněn krátkými a poté byl na začátek každé další stránky vypisován poloviční blok doplněný krátkými bloky. Pokud zbyly nějaké samotné poloviční bloky, byly přidané na konec dokumentu.
Jakési teoretizování nad tím, že by možná někdy mohl jeden půlblok vyjít hned po dlouhých apod. nemělo smysl rozvíjet, neb délky bloků v jednotlivých dokumentech byly různé přičemž dlouhé byly vzácné a nejvíce bylo krátkých, čili i osamnělé koncové půlboky byly spíše jen teoretická možnost. Krom toho jsem si říkal, že to už bych kdyžtak řešil ve variantě která by řešila úplně optimální rozložení.
Seřazení krátkých a polovičních bylo provedeno řazením polem. A pro vepisování bloků byla použita struktura pole polí, v podstatě taková větev byť i zde je to nazýváno strom s větvemi.
Strom pro stromové pakování
Velikost stránky je například 21 a bloky textů jsou vysoké: 10, 3, 12, 2, 22, 3, 2, 5, 8, 18, 17, 10, 2, 13, 2, 3
Dlouhé bloky (>21): 22
Poloviční bloky (>10,5 & <=21): 12, 18, 17, 13
Krátké bloky (<10,5): 10, 3, 2, 3, 2, 5, 8, 2, 2, 3
Základ stromu pro krátké bloky je poté takovýto:
První položka ve větvi je počet bloků dané velikosti, poslední položkou je pole s jednotlivými bloky textu (v dokumentu objekty s kopiemi jednotlivých bloků získané metodou .getTransferableForTextRange()).
Hlavní trik
Gró pro celý postup je: vepsat rovnou největší možný blok. Pro což se nabízí možnost kdy nyní nulové indexy jsou v kmeni nahrazeny ukazateli
na nejbližší kratší větev, což eliminuje opakované tupé porovnávání jednotlivých velikostí bloků či místa v kontejnerech.
Použití stromu: Zjištěné prázdné místo na stránce je index ve stromu. Je-li index pole, jedná se o větev s vhodným blokem, je-li index číslo, vhodná větev je na pozici index+ukazatel.
Například na stránce je volné místo o velikosti 10, zkontroluj jaký typ je index na pozici 10. Jde o pole takže zapiš blok z této větve.
Nebo volné místo je 7 a tam je číslo -2, čili vhodný blok je na pozici 7–2=5.
Samozřejmě je tam podmínka pro místo delší než strom → je-li volné místo větší tak použij nejvyšší větev.
A bylo-li volné místo menší než nejmenší větev, tak bylo vloženo Zalomení Stránky namísto oddělovače.
Ukazatele však musí být změněny když dojde na vyprázdnění větve (dovypsání všech bloků z dané větve).
Například byla vyprázdněna větev 8, je tedy potřeba přeindexovat větve 8 a 9.
Ale co se stane když dále bude vyprázdněná větev 5? Je potřeba mít správně všechny indexy směrem k nejbližší vyšší větvi, čili znovu přepsat minimálně 8 i 9.
Tento postup tedy znamená opakované přepisování indexů jestliže se objeví sekvence prázdných větví, přičemž silnou nevýhodou může být když kmen bude z tisíce a více indexů, neboť velké množství opakovaně přepisovaných indexů již může zpomalit výpočet.
Je sice pravda že počítače jsou mnohem rychlejší než dříve, ale také je pravda, že ve většině programů je množství všelijak neoptimalizovaných svinčíků a výsledkem jsou pomalé aplikace na rychlých zařízeních. A opakování neoptimalizovaných operací je plýtvání.
Výška stránky v ODT dokumentech je brána v setinách milimetrů, tudíž velikost A4 je 29 700 a polovina pro krátké bloky je 14 850, což je velikost pole při které možná již bude poznat v LibreBasicu nějaké to zpomalení bylo-li by opakovaně přepisováno několik tisíc indexů.
Ukazatele ve větvích
Aby bylo eliminováno vícenásobné přepisování kmenových ukazatelů, je možné přidat do větví ukazatele na sousední větve. Každá větev je tedy rozšířena o ukazatel na sousední levou větev (0 znamená nemá souseda vlevo) a ukazatel na sousední pravou větev (0 znamená nemá souseda vpravo).
A může začít kombinatorické předpeklí. Co se stane jestliže nastane sekvence prázdných větví? Bude tam víc skoků přes prázdné větve.
Například byly vyprázdněné větve 5 a 8. A další prázdné místo je 9, což potřebuje skoky -1, -3, -2 aby byla vybrána nejbližší nejkratší větev 3.
Ale kdyby bylo prázdné místo 10 což je nejvyšší větev? Blok z větve 10 bude zapsán a větev vyprázdněna, ale nalezení nové největší větve 3 potřebuje skoky -2, -3, -2.
Otázka tedy je: Je možné redukovat počet skoků?
Je, změněním ukazatelů ve vyprázdněné větvi.
Jednoduché měnění větvových ukazatelů
Kupříkladu byla vyprázdněna větev 5. Tudíž změnit indexy v sousedních větvích aby byla někdy déle přeskočena ona prázdná větev, což ale také nikdy nemusí nastat.
​Nový ukazatel v levé sousedce je počítán: původní pravý ukazatel z levé sousedky (2) plus pravý ukazatel z vyprazdňované větve (3) čili 2 + 3 = 5.
​Nový ukazatel v pravé sousedce: původní levý ukazatel z pravé sousedky (-3) plus levý ukazatel z vyprazdňované větve (-2) což je -3 -2 = -5.
Nějaké větvové ukazatele jsou sice změněné, ale může to být jen částečná výhra, neboť kombinatorické předpeklí může přejít do kombinatorického pekla → Ale vždyť by tam přeci mohlo být více skoků a to když další prázdné místo bude třeba tam nebo třeba onde anebo třebas …?
Ještě tedy příklad na další multiskoky, vyprázdněné větve budou 8 a poté 3.
Makro pro LibreOffice …
vykresluje v Calcu ukázkový strom a větvové ukazatele pro zadávané pořadí vyprazdňovaných větví. Např. pro 3, 5, 8 budou ukazatele jiné než pro výše uváděné 5, 8, 3.
privateiMIN&, iMAX&, oDOC as object'pro vykreslování v CalcuSubSledovatStrom'strom s krátkými blokyon local error gotobugdimi&, pShort() as variant, iShort&, p(), iBlocks&, iCount&, iSpace&, num&, min&, max&constcVariant="Variant()"'typ proměnné pro detekci větví ve stromu rem možnost dát krátké velikosti bloků do pShortpShort=array(2,3,5,8,10)'krátké blokyiShort=ubound(pShort)iMIN=pShort(0):iMAX=pShort(iShort)rem naplnit větve blokydimindex&, item as variant, pBranch()dimtree(iMIN to iMAX) as variant'inicializovat pole pro strommin=iMIN:max=iMAXfor eachnum in pShortindex=numitem=tree(index)ifTypeName(item)=cVariant then'přidat do stromui=item(0)item(0)=i+1'zvětšit počet bloků ve větvielse'vytvořit větev ve stromutree(index)=array(1, 0, 0)'položky ve větvi: (0): počet bloků ve větvi; (1): nejbližší větev vlevo; (2): nejbližší větev vpravoend ifnextrem nastavit ukazatele ve stromudimbNext as boolean, j&fori=lbound(tree) to ubound(tree)'index ve stromuitem=tree(i)ifTypeName(item)<>cVariant then'v tomto indexu není větevif NOTbNext then'dát 0 do indexů < minimální větevtree(i)=0else'nastavit ukazatel v kmeni na nejbližší kratší větevj=j-1tree(i)=jend ifelse'index je větevifj<>0 then'větve mají nějaké ukazatele mezi sebouj=j-1tree(i)(1)=j'nastavit levý ukazatel aktuální větve na předchozí větevtree(i+j)(2)=Abs(j)'nastavit pravý ukazatel v předchozí větvi na aktuální větevelseifbNext=true then'větev je přesně za sousední větví (není žádný ukazatel mezi větvemi)tree(i)(1)=-1'aktuální levý ukazateltree(i-1)(2)=1'pravý ukazatel předchozí větveend ifbNext=truej=0end ifnextirem simulace zápisu blokůdimbranch(), branch2(), iLeft&, iRight&, bBreak as boolean, iPage&, iPointerR&, iPointerL&iBlocks=ubound(pShort)'počet blokůj=2*iCountwhileiBlocks>-1tree2calc(tree)'vykreslit strom v CalcuiSpace=CLng(inputbox("Empty space (block size to ""write"")"))'zadat prázdné místoifiSpace=0 then exit sub'není prázdné místo iSpaceifiSpace<min then exit sub'příliš malé prázdné místoifiSpace>max then iSpace=max'volmé místo je větší než nejvyšší větev, použít nejvyšší větevitem=tree(iSpace)'ukazatel nebo větev ve stromuifTypeName(item)=cVariant then'položka v kmeni je větevbranch=itemindex=iSpaceelse'položka je číslo takže ukazatelindex=iSpace+itembranch=tree(index)'nejbližší kratší větevend ifrem získat blokyi=branch(0)'počet bloků ve větviwhilei=0'prázdná větev tak (multi)skok na nejbližší neprázdnou levou větevindex=index+branch(1)branch=tree(index)i=branch(0)'počet bloků ve větviwendi=i-1'poslední blok ve větvi rem v originálu je zde vložení bloku do dokumentuiBlocks=iBlocks-1'block is pasted rem modifikace "dotčených" větvíj=j+1branch(0)=i'snížit počet bloků ve větviifi=0 then'byl to poslední blok z větveifindex=max then'je to poslední větevp=findNearestBranch(tree, index, true)'nejbližší neprázdná levá větevbranch2=p(0)'nová nejvyší větevindex=p(1)max=index'nové maximumelseifindex=min then'jde o první větevp=findNearestBranch(tree, index, false)'nejbližší neprázdná pravá větevbranch2=p(0)'nová nejnižší větevindex=p(1)min=index'nové minimumelse'jde o prostřední větev rem nastavit ukazatel v pravé větviiRight=branch(2)'ukazatel na nejbližší pravou větevbranch2=tree(index+iRight)'pravá věteviPointerR=branch2(1) + branch(1)branch2(1)=iPointerR'změnit levý ukazatel v pravé větvi rem nastavit ukazatel v levé větviiLeft=branch(1)'ukazatel na nejbližší levou větevbranch2=tree(index+iLeft)'levá věteviPointerL=branch2(2) + branch(2)branch2(2)=iPointerL'změnit pravý ukazatel v levé větviend ifend ifwendtree2calc(tree)'poslední vykreslení stromuexit subbug:msgbox(Err & chr(13) & "line: " & Erl & chr(13) & Error, "WatchTree")End SubSubbug(sFce$)'chybová zprávamsgbox(Err &": " & Error & chr(13) & "Line: " & Erl, 16, sFce)stopEnd SubFunctionfindNearestBranch(tree(), ByVal index&, bLeft as boolean) as array'najít nejbližší větev; bLeft je pro levou větev jinak pravouon local error gotobugdimbranch(), iPointer&, iLeft%, iLeft2%iLeft=iif(bLeft,1, 2)branch=tree(index)doiPointer=branch(iLeft)'ukazatel na další větevindex=index+iPointerbranch=tree(index)'předpokládaná nová větevloop whilebranch(0)=-1'prázdná větev takže testni další věteviLeft2=iif(bLeft,2, 1)branch(iLeft2)=0'nová minimální nebo maximální větevfindNearestBranch=array(branch, index)exit functionbug:bug("findNearestBranch")End FunctionSubtree2calc(tree as object)'ukázat aktuální strom v Calcuon local error gotobugconstcVariant="Variant()"dimoSheet as object, i&, j&, oCur as object, oRange as object, oCell as object, ibound&, oBorder as new com.sun.star.table.BorderLine, cGrey&, item as variantcGrey=RGB(150, 150, 150)ifisNull(oDOC) thenoDOC=Stardesktop.loadComponentFromUrl("private:factory/scalc", "_blank", 0, array())oSheet=oDOC.Sheets(0)withoBorder.OuterLineWidth=50.Color=cGreyend withibound=lbound(tree)fori=ibound to ubound(tree)'šedé rámečky pro kmenj=i-ibound+1oCell=oSheet.getCellByPosition(j,1)'indexy v kmeniwithoCell.BottomBorder=oBorder.TopBorder=oBorder.RightBorder=oBorder.LeftBorder=oBorderend withnextiend ifoSheet=oDOC.Sheets(0)oCur=oSheet.createCursoroCur.goToEndOfUsedArea(false)oRange=oSheet.getCellRangeByPosition(0, 0, oCur.RangeAddress.EndColumn, 0)oRange.clearContents(5)'jako v herní smyčce -> smazat všechno a vykreslit to znovuibound=lbound(tree)fori=ibound to ubound(tree)j=i-ibound+1oCell=oSheet.getCellByPosition(j,1)'indexy v kmeniwithoCell.String=i.CharColor=cGreyend withitem=tree(i)ifTypeName(item)<>cVariant then'ukazatele v kmenioCell=oSheet.getCellByPosition(j,1)oCur=oCell.createTextCursorwithoCur.gotoEnd(false).CharColor=RGB(254, 57, 189)'růžová.CharWeight=com.sun.star.awt.FontWeight.BOLD.String=" " &itemend withelse'větevoCell=oSheet.getCellByPosition(j,2)'počet bloků ve větviwithoCell.ParaAdjust=3.CharColor=RGB(0, 123, 1)'tmavě zelená.CharWeight=com.sun.star.awt.FontWeight.BOLD.String=item(0)end withoCell=oSheet.getCellByPosition(j,3)'ukazatel vlevowithoCell.ParaAdjust=3.CharColor=RGB(255, 138, 23)'oranžová.CharWeight=com.sun.star.awt.FontWeight.BOLD.String=item(1)end withoCell=oSheet.getCellByPosition(j,4)'ukazatel vpravowithoCell.ParaAdjust=3.CharColor=RGB(105, 131, 133)'šeděmodrá.CharWeight=com.sun.star.awt.FontWeight.BOLD.String=item(2)end withoCell=oSheet.getCellByPosition(j,5)'"bloky" ve větvíchoCell.CellBackColor=iif(item(0)>0, RGB(102, 204, 255),-1)'světlemodrá nebo průhlednáend ifnextiexit subbug:bug("tree2calc")End Sub
Zpět k původnímu kombipeklíčku.
A když teď bude volné místo 9? Pak by byly potřeba skoky -1, -5, -1 na větev 2.
A kdyby prázdné místo bylo 10? pak nalezení nové nejvyšší větve 2 je skokem -8.
Je možné redukovat multiskoky? Asi jo, ale vypadá to až příliš komplikovaně a zbytečně. Jediné alespoň drobet obhájitelné řešení které mě napadá by bylo změnit ukazatele během multiskoku, ale znamenalo by to pamatovat si všechny skákané větve a všem změnit ukazatele, což značí nějaké zpomalení které však pro nějaká běžná data nejspíš nebude znatelné.
Mělo by to však smysl? Myslím si že ne, strom je jen pro krátké větve a je možná větší šance že delší větve se budou dříve zkracovat a kratší též ubývat než že by byly vyprazdňovány jen prostřední větve a vznikaly tak nějaké dlouhé sekvence prázdných větví mezi nejdelšími a nejkratšími větvemi a přitom vyvstávaly samé prostřední mezery ze kterých by se skákalo na kratší větve.
A je úplně zbytečné vymýšlet jakási neexistující matematické pseudosituace typu: Nechť větve jsou takové a onaké a všelijak divné, a prázdná místa jinaká a dozaujatě onakle máklá, a kontejnery jakbysmet navíc dodávané v jakémsi echtmagoricky vyblouzněném pořadí, a k tomu ... !
neboť následná pseudořešení postavená na takovýchto (nechť si to v hlavě představíš tak‐a‐tak a na tom prý stojí pravda) výmyslech které přežívají jen v jakýchsi chorých memorizátorech a nikdy nepomohly rozhodně nic nezjednodušší, jen bezsmyslně zesložiťují (asi jako celá matematika).
Skoky ve statických polích by měly být rychlé, tudíž zpomalení pro milióny skoků by mohlo být maximálně v sekundách, přičemž je jisté, že zjišťování velikostí bloků a vykreslování je mnohem pomalejší než výpočty se skoky. A jistě nechcete prohazovat takové množství bloků v nějakém nelidském dokumentu :-).
2D a 3D struktury
Nemělo by být složité udělat si tři větve pro jednotlivé prostory (x, y, z) ve 3D a prostě vzít volné místo v x, k němu volné v y a k němu volné v z a předmět vložit. Byť zase už to pro někoho mohou být stěží představitelné struktury i to jak mezi těmi třemi větvemi nalinkovat jednotlivé bedýnky
.
Specifika pro LibreOffice
Pomalá pole v LibreBasicu
LibreBasic skutečně není nejrychlejším jazykem pro operace s poli → kvůli čemuž jsem v makrech použil mnohem statičtější než dynamičtější pole, neboť užití ReDim preserve pro převelikostnění pole při změně každé položky je příliš pomalé pro tisíce položek. Tudíž jsou předdefinované výchozí velikosti polí (globální proměnná iREDIM) a podmínka pro zvětšení pole když již nestačí, což je mnohem rychlejší. Plnění pole jednoduchým příkazem typu pole(i)=něco je prováděno skrze funkci extendArray.
Pole se zkopírovanými informacemi (.getTransferableForTextRange()) o jednotlivých blocích (index (3) ve větvi) s předdefinovanými velikostmi jsou poté zhruba takto:arr(0)=object1arr(1)=object2arr(2)=Emptyarr(3)=Empty
'… atd.
Zákeřnosti v dokumentech
Přepínané .lockControllers() a ComponentWindow.Visible
oDoc.lockControllers() nejde použít pro detekci velikostí bloků, neboť to vypíná výpočet pozice viditelného kurzoru (oVCur). Vykreslování je nejpomalejší, ale je možné notné zrychlení pomocí .Position.Y.lockControllers() když nebude propočítávána pozice oVCur. Pouze velké bloky a koncové poloviční bloky potřebují zjišťovat zbývající místo na stránce, takže pro tyto případy je okno dokumentu schováváno alespoň s CurrentController což vykreslování také urychlí i když zdaleka ne tolik jako .ComponentWindow.Visible=false.lockControllers().
Před vykreslením je nejprve spočítáno rozmístění běžných půlbloků a krátkých bloků do stránek (fce fillContainer a pole pContainers()), po čemž mohou být bloky zapisovány již s lockControllers() což je tedy mnohem rychlejší než neustálé zjišťování prázdného místa na stránce po každém zapsaném bloku pomocí oVCur..Position.Y
Není se tedy potřeba obávat nějakého toho problikávání dokumentu při zpracovávání.
Úskalí při zjišťování velikostí bloků
Bylo to komplikované a nevím jestli jsem objevil všechny záludnosti, pro nějaké další použití je potřeba dobré testování.
Mezera mezi stránkami
Writer vykresluje stránky tak, že mezi nimi nechává mezeru ve které se zobrazuje případné Zalomení stránky. Šířka této mezery se však započítává do pozice viditelného kurzoru, je tedy proveden výpočet pro velikost této mezery.
Horní okraj odstavce na začátku bloku
Ten dělá problém, protože pozice kurzoru je nějak propočítávána s tímto okrajem, ale při Copy&Paste tento okraj může zmizet. Nezkoumal jsem přesně jak se to chová, tudíž makro prostě odstraní tyto Horní okraje pro všechny odstavce v oDoc..Text
Horní a spodní okraj oddělovače
V realizovaném případě byla oddělovačem čára z mínusů a byl v ní vynulován horní i spodní okraj. Konstantou iDASHEDHEIGHT se mění velikost fontu pro mínusy a makro dopočítává počet mínusů aby nepřesáhly jeden řádek.
Volba Spojit s následujícím odstavcem
Ta dělá že se odstavec či tabulka mnohdy posune na další stránku neb na aktuální není pro tak dlouhý blok místo. Pro zjištění velikosti bloků makro odstraní tuto vlastnost z odstavců i tabulek a před každý blok vloží Zalomení stránky aby začínal na začátku stránky.
Tato operace však není vykreslována, neb Zalomení jsou vkládána při .lockControllers() a velikosti bloků zjišťovány při ComponentWindow.Visible=false.
Malá mezera pod tabulkou která přesune viditelný kurzor na začátek nové stránky.
Ale je dostatečně velká aby se do ní vtěsnal první řádek z následujícího bloku, protože ten následující řádek má menší velikost písma než viditelný kurzor.
Je problematické detekovat přesnou velikost takovýchto bloků, byť by se kurzoru dala nastavit velikost písma 1px a pak by se většinou do té malé mezery již vešel, avšak toto se týká jen půlbloků zabírajících v podstatě celou stránku a jelikož tedy není žádný další blok který by se do stránky vešel, je úplně jedno jestli má takový celoblok spočítanou velikost přesně na pixel či nikoliv.
Tahle malá mezera však dokáže způsobit to, že se na konci dokumentu může objevit zbytečná prázdná stránka, která by se stala nejspíš otravnou při tisku. To je dáno tím že po tabulce musí být odstavec a ten má tedy nějakou výšku, která může být větší než mezera.
Pokud tedy na toto dojde, makro sníží velikost posledního řádku na 1px (což ale nejde nastavit ručně neb nejmenší ruční velikost lze dát 2, tak doufám že to nebude dělat problémy) a pokud se ani takto zmenšený řádek nevejde pod tabulku na předchozí stránku, makro zkusí dát horní a spodní okraj v prvním řádku tabulky na nulu (neb přeci jen tyto okraje v textu v tabulkách nějaké byly). A pokud i tak zůstane poslední prázdná stránka, vypíše se alespoň upozorňující hláška.
Tabulka rozdělená na dvě stránky
Znamená že oVCur bude opět o něco větší, ovšem jelikož se to týká jen velkých bloků tak jsem velikost těch malých mezer nezjišťoval..Position.Y
Zalomení stránky někdy potřebuje alespoň jeden znak na prvním řádku stránky jinak se nevloží
To je řešeno vkládání Nulových mezer U+200B (&h200B&) na začátek každého bloku začínajícího na začátku stránky. Prostě vložit nulovou mezeru s malou velikostí písma, po ní Zalomení stránky a pak již blok.
Závažná chyba
Raději jsem kontroloval počet tabulek, neb blok se skládal z nějakého textu v hlavičce a následné tabulky. A samozřejmě by nemělo být dopuštěno aby byl nějaký blok vynechán.
Uvedená hláška informující že jsem někde udělal chybu se mi naštěstí neobjevila mnohokrát, byť nevím zda-li jsem objevil všechny záludnosti
nebo praxe ukáže ještě nějaký problém.
Snažší debugování
Zpočátku to bylo krokování programu než jsem vychytal záludnosti
, tudíž jsem si to usnadnil tím, že jsem přidal možnost aby se očíslovaly oddělující čáry z mínusů, neboť neustálé rolování myší a počítání tabulek a řádků bylo dost unavující.
Makro
Je možné si zkopírovat do maker kód níže a spustit makro ReducePages, které otevře dialog pro výběr souboru kde vybrat stažený prohazet-bloky.odt35kB. Makro vytvoří kopii tohoto souboru a zpřehází v něm bloky textu.
option explicitrem testováno v LibreOffice 25.2.3.2 Win10x64 2025.5 rem pro jistotu jsou zapsány ByRef pro proměnné měněné uvnitř funkcí! rem reguláry pro označení bloků, blocky musí být v normálním textu a nikoliv tabulkách apod.private constsBLOCKSTART="IN THE COURT OF"'začátek blokuprivate constsBLOCKEND="^-+$"'konec bloku (Čárkovaná čára z mínusů) rem přípona pro zredukovaný dokumentprivate constsRENAME="-REDUCED"'přípona pro nový dokument po redukci počtu stránek např.: muj-dokument-REDUCED.odt rem výchozí adresář pro výběr souboru na začátku makraprivate constsINITDIR=""'např. d:\documents or file:///home/user rem praxe ukázala že výška Čárkované čáry může redukovat stránky navíc, tudíž jsem její výšku snížil na 9 (z 12)private constiDASHEDHEIGHT=9'výška Čárkované čáryprivate constsDASHEDFONT="Liberation Serif"'výchozí font pro Čárkovanou čáru, aby všechny měly opravdu stejnou výškuprivate constiSOLITARY=3'násobek iDASHEDHEIGHT pro minimální místo po osamnělém půlbloku kam může být vložena a rozpojena další část hlavičky IN THE COURT OF rem konstanty makraprivate constDEBUG=false'true: přidá číslo před Čárkovanou čáru (pro snažší debuging)privateiNO&'číslo před Čárkovanou čárou během debugování (je pouze v: Sub pasteDashedLine, pasteDashedLine2) rem proměnné nastavované v getGlobals()privateoDASHEDas object'objekt s Čárkovanou čárou pro .insertTransferableprivateiDASHED&'velikost čárkované čáry v 1/100mmprivateiHEIGHT&'velikost stránky v 1/100mmprivateiHEIGHTED&'velikost stránky bez horního a spodního okraje 1/100mmprivateiGAP&'mezera mezi stránkami 1/100mmprivateiHALFED&' "polovina" stránky 1/100mm rem proměnné nastavované v getBlocks()privateiMIN&'skutečná nejkratší velikost z krátkých blokůprivateiMAX&'skutečná největší velikost z krátkých blokůprivateiMINHALF&,iMAXHALF&'minimální and maximální velikost půlblockůprivate constiREDIM=50'pro ReDim Preserve aby to nekolabovalo na příliš malých polích (výchozně 50; pro tisíce bloků může být 100 a víc)privateiCONT&'počet kontejnerů (stránek)privateiSAFE&'počet tabulek (pro kontrolu že je ve vybraném a zredukovaném dokumentu stejný počet tabulek) rem řetězcové konstanty (většinou pro rychlejší operace s řetězci)private constsNULL="",sMINUS="-",sPAGESTYLES="PageStyles",sVARIANT="Variant()"'typ proměnné pro detekci větví ve stromuSubReducePages'proházet bloky textu pro zredukování počtu stránekon local error gotobugconstcStep=30'počet bloků pro aktualizaci ukazatele průběhu ve stavovém řádkudimoDocas object,oCuras object,oVCuras object,oStatusbaras object,oFoundas object,i&,pBig() as variant,iBig&,pHalf(),iHalf&,pShort() as variant,iShort&,p(),_oBlockas object,iBlocks&,iTables&,iCount&,dataas object,arr(),iSpace&,sUrl$,sUrlNew$,undoMgras object,iPages0&,iPagesBig&,ibound&,iContainer&,_pContainers(iREDIM) as variant,pp(),branch(),ii&,iProgress&,iContainers&rem vytvořit kopii dokumentusUrl=chooseFile(sINITDIR)sUrlNew=Mid(sUrl,1,Len(sUrl)-4) &sRENAME&".odt"'přidat -REDUCED.odt do url doumentuFileCopy(sUrl,sUrlNew)oDoc=StarDesktop.loadComponentFromURL(sUrlNew,"_blank",0,array())'otevřít ...-REDUCED.odtiPages0=oDoc.CurrentController.PageCount'zapamatovat výchozí počet stránekundoMgr=oDoc.UndoManagerundoMgr.lockiCount=oDoc.TextTables.Count'předpokládaný počet bloků je počet tabulek, také jako maximum pro ukazatel průběhuiSAFE=iCount'počet tabulek v originálním dokumentu (pro kontrolu)oStatusbar=oDoc.CurrentController.StatusIndicator'ukazatel průběhuwithoStatusbar'inicializace ukazatele průběhu.start(sNULL,3*iCount) .setValue(0) end withoCur=oDoc.Text.createTextCursoroVCur=oDoc.CurrentController.ViewCursorwithoDoc.CurrentController.Frame.ComponentWindow.Visible=false'viditelný kurzor musí být aktivní (nedodává Position.Y při oDoc.lockControllers).ContainerWindow.toFront'dokument raději na popředí (někdy je problematické spouštět makro z Basic editoru když má v dokumentu skákat viditelný kurzor)end withgetGlobals(oDoc,sBLOCKEND)'nastavit část globálních proměnnýchoDoc.lockControllersremoveKeepTogether(oDoc)'odebrat vlastnost "Svázat s následujícím odstavcem"p=getBlocks(oDoc,sBLOCKSTART,sBLOCKEND,oStatusbar,iCount,cStep)'dostat bloky a zbytek globálních proměnnýchpBig=p(0)'bloky delší než stránkaiBig=ubound(pBig)'počet velkých blokůpHalf=p(1)'půlblokyiHalf=ubound(pHalf)'počet půlblokůifiHalf>-1thensortHalfBlocks(pHalf)pShort=p(2)'krátké blokyiShort=ubound(pShort)'počet krátkýchrem vyplnit větve krátkými blokyifiShort>-1then'jsou krátké bloky tak vytvořit stromdimindex&,itemas variant,min&,max&,pBranch() dimtree(iMINtoiMAX) as variant'inicializace pole pro kmen stromumin=iMIN:max=iMAXfor eacharrinpShort'jednotlivé subpole z pole krátkých blokůindex=arr(0)item=tree(index) ifTypeName(item)=sVARIANTthen'jde o větev stromui=item(0)ii=i+1item(0)=ii'zvýšit počet bloků ve větvi (tento počet je pro plnění kontejnerů)pBranch=item(3)'pole s bloky ve větvi (.getTransferableForTewxtRange())ibound=ubound(pBranch) ifi>iboundthen'větev je již plná bloků tak zvěti její poleredim preservepBranch(ibound+iREDIM)'zvětšit pole ve větviitem(3)=pBranchend ifitem(3)(i)=arr(1)'přidat blok do větveelse'vytvořit větev ve stroměredimp(iREDIM)p(0)=arr(1)tree(index)=array(1,0,0,p)'indexy ve větvi:'(0): počet bloků ve větvi'(1): ukazatel na větev vlevo'(2): ukazatel na větev vpravo'(3): pole s .getTransferable() of blokůend if nextrem nastavit ukazatele v kmeni a větve ve stromudimbNextas boolean,j& fori=lbound(tree) toubound(tree)'index v kmeniitem=tree(i) ifTypeName(item)<>sVARIANTthen'na tomto indexu nejde o větevif NOTbNextthen'dát 0 na indexy menší než minimální větevtree(i)=0else'nastavit ukazatel v kmeni na nejbližší nejkratší větevj=j-1tree(i)=jend if else'index je větevifj<>0then'větve mají mezi sebou nějaké ukazatelej=j-1tree(i)(1)=j'nastavit levý ukazatel v aktuální větvi na předchozího sousedatree(i+j)(2)=Abs(j)'nastavit pravý ukazatel v předchozí větvi na aktuální větevelseifbNext=truethen'větev je přesně za sousední větví (není mezi nimi index s ukazatelem)tree(i)(1)=-1'aktuální ukazatel vlevotree(i-1)(2)=1'předchozí větve pravý ukazatelend ifbNext=truej=0end if nextielse'žádné krátké blokyiMIN=iHALFEDend ifrem zapsat velké bloky do dokumentuoDoc.unlockControllersdimbHalfas booleanibound=ubound(pBig) fori=lbound(pBig) toibounddata=pBig(i)(1)'data blokupasteBlock(oDoc,oCur,data) ifi<iboundthen'není to poslední velký blokbHalf=pasteDashedLine(oDoc,iMIN,oCur,oVCur)'bylo tam Zalomení stránkyelse'poslední velký blokifiShort<>-1ANDiHalf<>-1thenbHalf=pasteDashedLine(oDoc,iMIN,oCur,oVCur)'je tam poloviční nebo krátký blokend if nextiiPagesBig=oDoc.CurrentController.PageCount'zapamatovat stránku s koncem posledního velkého blokuifiShort<>-1then'jsou krátké blokyrem naplnit kontejnery - to je výpočet pro každou stránku aby bylo možné rychlé vykreslování při .lockControllers() místo pomalého oVCur.Position.Y po každém blokuiSpace=getSpace(oDoc,oVCur)'zbývající prázdné místo na stránceoDoc.lockControllersifiShort>-1ANDiSpace>=iMINtheniContainer=iSpace'je tam nějaké místo po velkých blocíchiBlocks=ubound(pShort)'počet krátkých blokůifiSpace<iHEIGHTEDthenfillContainer(pContainers(),tree(),iBlocks,min,max,-1,pHalf,iSpace)'doplnit krátkými blokywhileiBlocks>-1'spočítat normální stránky s půl a krátkými blokyfillContainer(pContainers(),tree(),iBlocks,min,max,iHalf,pHalf(),iHEIGHTED) wend redim preservepContainers(iCONT-1)rem zapsat krátké a půlbloky z pole pContainers()iProgress=2*iCount'pro ukazatel průběhuiContainers=ubound(pContainers) fori=lbound(pContainers) toiContainersiProgress=iProgress+1ifiProgressMODcStep=0thenoStatusbar.setValue(iProgress)p=pContainers(i)'indexy ve stromě s bloky do aktuální stránkyibound=ubound(p) forj=lbound(p) toiboundpp=p(j)iHalf=pp(0)iShort=pp(1) ifiHalf<>-1then'zapsat půlblokpasteBlockHalf(oDoc,pHalf,iHalf,oCur) ifiShort<>-1thenpasteDashedLine2(oDoc,oCur)'půlblok není samostný na stránce tak přidat Čárkovanou čáruend if ifiShort<>-1then'zapsat krátký blokbranch=tree(iShort)iCount=branch(0)'který blok zapsatdata=branch(3)(iCount)'.getTransferable()pasteBlock(oDoc,oCur,data)tree(iShort)(0)=iCount+1'přístě zapsat blok s vyšším indexemifj<iboundthenpasteDashedLine2(oDoc,oCur)'nejde o poslední blok tak přidat Čárkovanou čáruend if nextjifi<iContainerstheninsertPageBreak(oCur)'nejde o poslední kontejner takže vložit Zalomení stránkynextiend ifrem zapsat zbylé osamnělé půlbloky (budou rozpojovány přes stránky)dims$,s1$,iSplitted& ifiHalf>-1theniSplitted=oDoc.CurrentController.PageCount'stránka s 1. zbývajícím půlblokems=chr(13) &chr(13) &"Solitary half blocks: "&iHalf&chr(13)'počet osamnělých půlblokůs1=chr(13) &"from page: "&iSplittedifoDoc.hasControllersLockedthenoDoc.unlockControllers'musí být počítáno prázdné místo pro Čárkovanou čárus=s&"SPLITTED"&s1'info o osamnělých půlblocíchiSpace=iDASHED*iSOLITARY'minimální místo pro řádek hlavičky IN THE COURT OFwhileiHalf>-1'zapsat blokypasteDashedLine(oDoc,iSpace,oCur,oVCur)pasteBlockHalf(oDoc,pHalf,iHalf,oCur)'vložit půlblokwend end ifrem smazat poslední prázdnou stránku (vyskytla-li se)dimiPages&,iPages2& ifoDoc.hasControllersLockedthenoDoc.unlockControllerswithoVCur.jumpToLastPage.jumpToStartOfPageend with ifisEmpty(oVCur.Cell) then'viditelný kurzor není v tabulcewithoCur.gotoRange(oVCur.Start,false) .gotoEnd(true) end with ifoCur.String=sNULLthen'poslední stránka je prázdnáiPages=oDoc.CurrentController.PageCount'aktuální počet stránekwithoVCur'snížit velikost fontu aby kurzor snad skočil pod tabulku na předchozí stránce.String=chr(&h200B&)'nulová mezera.CharHeight=1'není možné nastavit manuálně neb minimální manuální jde 2.CharHeightAsian=1.CharHeightComplex=1.gotoRange(oCur.End,false) end with ifoDoc.CurrentController.PageCount=iPagesthen'poslední stránka nebyla eliminovánawithoVCur.jumpToLastPage.jumpToPreviousPage.jumpToEndOfPageend with if NOTisEmpty(oVCur.Cell) then'viditelný kurzor je v poslední tabulcedimoTableas object,oCellas object,oCursoras objectoTable=oVCur.TextTablefori=0tooTable.Columns.Count-1'žádný horní a spodní okraj pro odstavce v prvním řádku tabulkyoCell=oTable.getCellByPosition(i,0)oCursor=oCell.createTextCursorwithoCursor.gotoStart(false) .gotoEnd(true) .ParaTopMargin=0.ParaBottomMargin=0end with nextiend if end if end if end ifrem ukázat statistikuremoveLock(oDoc)iPages2=oDoc.CurrentController.PageCount'aktuální počet stráneki=iPages0-iPages2'počet ušetřených stráneks="Saved pages: "&i+iif(iPages2=iPages,1,0) &iif(iBig=-1,sNULL,chr(13) &"Big blocks end in page: "&iPagesBig) &siCount=oDoc.TextTables.Count'počet vložených tabulekifiCount<>iSAFEthenproblem(oDoc,"MISSING TABLES: "&iSAFE-iCount,16) ifiPages2=iPagesthen'bohužel zůstala poslední prázdná stránka (a nepředpokládám že je potřeba nějaké složitější snaha jak ji smazat)s=s&chr(13) &chr(13) &"LAST PAGE IS EMPTY"withoVCur.jumpToLastPage.jumpToPreviousPage.jumpToStartOfPageend with elseifiBig>-1then'nějaké velké bloky byly tak skočit na stránku kde končí poslední z nichwithoVCur.jumpToPage(iPagesBig) .jumpToStartOfPageend with while NOTisEmpty(oVCur.Cell)'viditelný kurzor pod poslední velký blokoVCur.goDown(1,false) wend elseifiSplitted>0then'osamnělé půlbloky tak viditelný kurzor na jejich začátekwithoVCur.jumpToPage(iSplitted) .jumpToEndOfPageend with end ifmsgbox(s,iif(i<0,48,0)) exit subbug:removeLock(oDoc)bug("ReducePages") End Sub Subbug(sFce$)'chybové hlášenímsgbox(Err&": "& Error &chr(13) &"Line: "&Erl,16,sFce) stop End Sub FunctionchooseFile(optionalsInitDir$) as string'dialog pro výběr soubor ve výchozím adresářidimoFileDlgas object,oFileAccessas object,oFilesas object,sFile$oFileDlg=CreateUnoService("com.sun.star.ui.dialogs.FilePicker") withoFileDlg'filtr souorů v dialogu.AppendFilter("All files (*.*)","*.*") .AppendFilter("Writer (*.odt)","*.odt") .SetCurrentFilter("Writer (*.odt)")'výchozí soubor ve filtruend withoFileAccess=CreateUnoService("com.sun.star.ucb.SimpleFileAccess") withoFileDlg.MultiSelectionMode=false.SetDisplayDirectory(ConvertToUrl(sInitDir)) end with ifoFileDlg.execute() then'dialog pro výběr souboruoFiles=oFileDlg.getFiles()sFile=oFiles(0) end if ifsFile=""then stop'soubor nebyl vybránchooseFile=sFileEnd Function FunctiondeleteDocument(oDocas object)'smazat obsah dokumentuon local error gotobugdimoCuras object,oVCuras objectoVCur=oDoc.CurrentController.ViewCursoroCur=oDoc.Text.createTextCursorwithoCur.gotoStart(false) .gotoEnd(true) .String=sNULL.setAllPropertiesToDefault.ParaAdjust=3'zarovnání na středend with exit functionbug:bug("deleteDocument") End Function FunctionextendArray(p(),index&)'zvětšit příliš malé poleon local error gotobugdimibound&ibound=ubound(p)'délka poleifindex>iboundthen'nový index v poliredim preservep(index+iREDIM)'zvětšit poleend if exit functionbug:bug("extendArray") End Function FunctionfillContainer(pContainers(),tree(), ByRefiBlocks&, ByRefmin&, ByRefmax&, ByRefiHalf&,pHalf(), ByValiSpace0&)'naplnit kontejneron local error gotobugdimpCont(iREDIM) as variant,i&,branch(),branch2(),iLeft&,iRight&,bBreakas boolean,iPage&,iPointerR&,iPointerL&,bHalfas boolean,itemas variant,index&,iSpace&,_iCount&,ibound&,p()bHalf=iif(iHalf<>-1,true,false)'půlblokifbHalfthen'půlblok existujeiSpace0=iSpace0-iDASHED-pHalf(iHalf)(0)'prázdné místo po půlblokuiHalf=iHalf-1end if whileiBlocks>-1ANDiSpace0>=miniSpace=iSpace0ifiSpace>maxtheniSpace=max'prázdné místo je větší než největší větev tak použít největší větevitem=tree(iSpace)'ukazatel nebo větev ve stromuifTypeName(item)=sVARIANTthen'položka je větevbranch=itemindex=iSpaceelse'položka je číslo čili ukazatelindex=iSpace+itembranch=tree(index)'nejbližší kratší větevend ifrem dostat blokyi=branch(0)'počet bloků ve větviwhilei=0'prázdná větev takže (multi)skok na neprázdnou větev vlevoindex=index+branch(1)branch=tree(index)i=branch(0)'počet bloků ve větviwendi=i-1'poslední blok ve větviextendArray(pCont,iCount) ifbHalfthen'přidat poloviční a krátký blok do kontejnerupCont(iCount)=array(iHalf+1,index)bHalf=false'žádný dlaší půlblok do tohoto kontejneruelse'přidat pouze krátký blok do kontejnerupCont(iCount)=array(-1,index)'-1 že tam není půlblokend ifiCount=iCount+1iBlocks=iBlocks-1iSpace0=iSpace0-index-iDASHED'nové prázdné místo v kontejnerurem změnit "dotčené" větvebranch(0)=i'snížit počet bloků ve větviifi=0then'to byl poslední blok z větveifindex=maxthen'jde o poslední větevp=findNearestBranch(tree,index,true)'nejbližší neprázdná levá větevbranch2=p(0)'nová nejvyší větevindex=p(1)max=index'nové maximumelseifindex=minthen'jde o první větevp=findNearestBranch(tree,index,false)'nejbližší neprázdná pravá větevbranch2=p(0)'nová nejnižší větevindex=p(1)min=index'nové minimumelse'jde o prostřední větevrem nastavit ukazatel v pravé větviiRight=branch(2)'ukazatel na pravou větevbranch2=tree(index+iRight)'pravá věteviPointerR=branch2(1) +branch(1)branch2(1)=iPointerR'změnit levý ukazatel v pravé větvirem nastavit ukazatel v levé větviiLeft=branch(1)'ukazatel na levou větevbranch2=tree(index+iLeft)'levá věteviPointerL=branch2(2) +branch(2)branch2(2)=iPointerL'změnit pravý ukazatel v levé větviend if end if wendrem přidat blok do kontejneruifbHalfANDiCount=0then'pouze jeden půlblok do kontejnerupCont=array(array(iHalf+1,-1)) else'více bloků v kontejneruredim preservepCont(iCount-1) end ifextendArray(pContainers,iCONT)pContainers(iCONT)=pContiCONT=iCONT+1exit functionbug:bug("fillContainer") End Function FunctionfindNearestBranch(tree(), ByValindex&,bLeftas boolean) asarray'najít nejbližší větev; bLeft je pro levou jinak pravouon local error gotobugdimbranch(),iPointer&,iLeft%,iLeft2%iLeft=iif(bLeft,1,2)branch=tree(index) doiPointer=branch(iLeft)'ukazatel na další větevindex=index+iPointerbranch=tree(index)'předpokládaná nová větevloop whilebranch(0)=-1'prázdná větev tak testnout dalšíiLeft2=iif(bLeft,2,1)branch(iLeft2)=0'nová minimální či maximální větevfindNearestBranch=array(branch,index) exit functionbug:bug("findNearestBranch") End Function FunctiongetBlocks(oDocas object,sBLOCKSTART$,sBLOCKEND$,oStatusbaras object,iCount&,iStep&) asarray'dostat velikosti a .getTransferable() blocků; zbytek glob.proměnnýchon local error gotobugdimoDescas object,oFoundas object,dataas object,p(iCount),i&,oCuras object,oVCuras object,oEnumas object,ibound&oCur=oDoc.Text.createTextCursoroVCur=oDoc.CurrentController.ViewCursorrem možné přes .lockControllers()rem odstranit možná Zalomení stránky z odstavců a tabulekoEnum=oDoc.Text.createEnumerationwhileoEnum.hasMoreElementsoFound=oEnum.nextElementifoFound.supportsService("com.sun.star.text.Paragraph") then'odstavecoFound.BreakType=0elseifoFound.supportsService("com.sun.star.text.TextTable") then'tabulkaoFound.BreakType=0end if wendrem najít první začátek blokuoDesc=oDoc.createSearchDescriptorwithoDesc.SearchRegularExpression=true.SearchString=sBLOCKSTARTend withoFound=oDoc.findFirst(oDesc)'najít první začátek blokuifisNull(oFound) thenproblem(oDoc,"No Start of block!",16)rem zkopírovat bloky do poledimbEndas booleanoFound=oFound.Start'najít též 1. blokwhile NOTbEndrem najít začátek blokuoDesc.SearchString=sBLOCKSTARToFound=oDoc.findNext(oFound.End,oDesc) if NOTisNull(oFound) then if NOTisEmpty(oFound.Cell) then'začátek bloku je v tabulceoDoc.CurrentController.select(oCur)problem("Start of block is in Table!",16) end if withoCur.gotoRange(oFound.Start,false)'kurzor na začátek bloku (pro očekávanou kopii).ParaTopMargin=0'nulový horní okraj odstavceend withrem najít konec blokuoDesc.SearchString=sBLOCKENDdo'pro jistotu ignorovat konce bloků v tabulkáchoFound=oDoc.findNext(oFound.End,oDesc) ifisNull(oFound) thenoCur.gotoEnd(true)oDoc.CurrentController.select(oCur) ifoDoc.hasControllersLockedthenoDoc.unlockControllersif NOToDoc.CurrentController.Frame.ComponentWindow.isVisiblethenoDoc.CurrentController.Frame.ComponentWindow.Visible=trueproblem("No End of block!",16) end if loop untilisEmpty(oFound.Cell)rem zkopírovat blokoCur.gotoRange(oFound.Start,true)extendArray(p,i)p(i)=oDoc.CurrentController.getTransferableForTextRange(oCur)'blok jako .getTransferable()withoCur'dát Zalomení stránky po bloku.collapseToEnd.BreakType=com.sun.star.style.BreakType.PAGE_AFTER'vložit Zalomení stránkyend withi=i+1ifiMODiStep=0thenoStatusbar.setValue(i)'aktualizovat ukazatel průběhuelse'nenalezen blok tak konecbEnd=trueend if wend redim preservep(i-1)rem zjistit velikosti blokůdimy&,iPage&,pBig(iREDIM),pHalf(iREDIM),pShort(iREDIM),iBig&,iHalf&,iShort&,iSize&,j&,oBlockas objectiMIN=iHALFED:iMAX=0:iMINHALF=iHALFED:iMAXHALF=0rem vlastnost Position viditelného kurzoru oVCur musí být přes .unlockControllers()ifoDoc.hasControllersLockedthenoDoc.unlockControllersfori=lbound(p) toubound(p)oBlock=p(i)'jeden blokj=j+1withoVCur.jumpToPage(j) .jumpToStartOfPagey=.Position.Y'počásteční pozice blokuiPage=.Page'stránka na začátku bloku.jumpToEndOfPageend with while NOTisEmpty(oVCur.Cell)'není-li viditelný kurzor v tabulce tak jde o velký blokwithoVCur.jumpToNextPage.jumpToEndOfPagej=j+1end with wendiPage=oVCur.Page-iPage'počet stránek pro blokiSize=oVCur.Position.Y-yifiPage=0then'krátký nebo půlblokifiSize>iHALFEDthen'blok je delší jak půl stránky ale kratší než stránkainsertItemToBranch(pHalf,iHalf,iSize,oBlock) ifiSize<iMINHALFtheniMINHALF=iSizeifiSize>iMAXHALFtheniMAXHALF=iSizeelse'krátký blokinsertItemToBranch(pShort,iShort,iSize,oBlock) ifiSize<iMINtheniMIN=iSize'nejmenší krátký blokifiSize>iMAXtheniMAX=iSize'největší krátký blokend if else'půlblok (ale na celoiu stránku) nebo velký blokiSize=iSize-iPage*iGAPifiSize<=iHEIGHTthen'půlblok na celou stránkuinsertItemToBranch(pHalf,iHalf,iHEIGHTED,oBlock)'! možná iSize namísto iHEIGHTED !ifiSize<iMINHALFtheniMINHALF=iHEIGHTEDifiSize>iMAXHALFtheniMAXHALF=iHEIGHTEDelse'velký blok (delší než stránka)insertItemToBranch(pBig,iBig,iSize,oBlock) end if end if nextirem nastavit skutečné velikosti políifiBig=0thenpBig=array() else'velké bloky jsou vzácné ale vždy přesahují velikost stránky takže pro ně není dělána žádná optimalizaceredim preservepBig(iBig-1)'velké blokyend if ifiHalf=0thenpHalf=array() else redim preservepHalf(iHalf-1)'půlblokyend if ifiShort=0thenpShort=array() else redim preservepShort(iShort-1)'krátké blokyend ifoDoc.lockControllersdeleteDocument(oDoc)getBlocks=array(pBig,pHalf,pShort) exit functionbug:bug("getBlocks") End Function SubgetGlobals(oDocas object,sBLOCKEND$)'nastavit nějaké globální proměnnéon local error gotobugdimoDescas object,oFoundas object,oCuras object,oVCuras object,oBorderas newcom.sun.star.table.BorderLine2,y&,iPages&,oPageas object,s$oCur=oDoc.Text.createTextCursoroVCur=oDoc.CurrentController.ViewCursoroDesc=oDoc.createSearchDescriptorwithoDesc.SearchString=sBLOCKEND.SearchRegularExpression=true.SearchBackwards=trueend withrem přidat poslední stránku pouze s Čárkovanou čárou pro vytvoření jednotné Čárkované čáryoCur.gotoEnd(false)oFound=oDoc.findNext(oCur.Start,oDesc)'Čárkovaná čáraifisNull(oFound) thenproblem(oDoc,"No Dashed line in document")'v dokumentu chybí Čárkovaná čárawithoFound'vlastnosti Čárkované čáryrem bez okrajů atd..setAllPropertiesToDefault.TopBorderDistance=0.TopBorder=oBorder.ParaTopMargin=0.ParaBottomMargin=0.CharHeight=iDASHEDHEIGHT.CharHeightAsian=iDASHEDHEIGHT.CharHeightComplex=iDASHEDHEIGHT.CharColor=RGB(0,0,0)'barva čáry.String=sMINUS'dát první mínus pro Čárkovanou čáruend withrem vyplnit číáru mínusyoVCur.gotoRange(oFound.End,false)y=oVCur.Position.YwhileoVCur.Position.Y=y'přidávat mínusy až se nevejdou na jednu řádkuwithoVCur.String=sMINUS.collapseToEndend with wend withoVCur'smazat pár mínusů aby byly jen na jedné řádce.goLeft(6,true) .String=sNULLend withrem zjistit výšku stránky a mezery mezi stránkamiwithoCur.gotoRange(oFound.Start,false) .BreakType=com.sun.star.style.BreakType.PAGE_BEFORE'vložit Zalomení stránky.gotoEndOfParagraph(true) end withoVCur.gotoRange(oFound.Start,false)y=oVCur.Position.YiPages=oDoc.CurrentController.PageCount-1'původní počet stránekifiPages=0thenproblem(oDoc,"Only one page")oPage=oDoc.StyleFamilies.getByName(sPAGESTYLES).getByName(oVCur.PageStyleName)'poslední stránkaiHEIGHT=oPage.Height'velikot stránky 1/100mmiHEIGHTED=iHEIGHT-oPage.TopMargin-oPage.BottomMargin'velikost stránky bez svislých okrajůiGAP=y/iPages-iHEIGHT'y=(iHEIGHT+iGAP) * iPages; 1/100mmoCur.gotoEndOfParagraph(true)oDoc.Text.insertControlCharacter(oCur.End,com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK,true)'vložit Enter a Čárkovanou čáruoVCur.gotoRange(oCur.End,false)iDASHED=oVCur.Position.Y-y'velikost Čárkované čáry v 1/100mmiHALFED=(iHEIGHTED-iDASHED) /2'polovina stránkywithoCur.BreakType=0'odstranit Zalomení stránky.ParaAdjust=3'zarovnání na střed.CharFontName=sDASHEDFONT'výchozí font Čárkované čáryend withoDASHED=oDoc.CurrentController.getTransferableForTextRange(oCur)'zkopírovat Čárkovanou čáruexit subbug:bug("getGlobals") End Sub FunctiongetSpace(oDocas object,oVCuras object) as long'získat zbývající prázdné místo na stránce (!!! bacha na vertikální okraje stránky či odstavců !!!)on local error gotobugdimiPage&,y&,iTop&,iBottom&,oPageStyleas object,iSpace&iPage=oVCur.Page'aktuální stránkaoPageStyle=oDoc.StyleFamilies.getByName(sPAGESTYLES).getByName(oVCur.PageStyleName)y=oVCur.Position.YiSpace=iPage*(iHEIGHT+iGAP) -iGAP-y'iPage*iHEIGHT + (iPage-1)*iGAP - ygetSpace=iSpace-oPageStyle.BottomMargin'bez spodního okraje stránkyexit functionbug:bug("getSpace") End Function SubinsertItemToBranch(p(), ByRefi&,iSize&,item())'přidat položku do větve; zvyšuje proměnnou i& (ByRef) !!!dimibound& on local error gotobugextendArray(p,i)p(i)=array(iSize,item)'vložit položkui=i+1exit subbug:bug("insertItemToBranch") End Sub SubinsertPageBreak(oCuras object)'vložit Zalomení stránky před oCuron local error gotobugwithoCur'insert Page Break.gotoEnd(false) .String=chr(&h200B&)'dát Nulovou mezeru na začátek prázdné stránky pro bezpečné Zalomení stránky.ParaTopMargin=0'bez Horního okraje odstavce.ParaBottomMargin=0.BreakType=com.sun.star.style.BreakType.PAGE_BEFORE.CharHeight=2'malá velikost nulové mezery.CharHeightAsian=2.CharHeightComplex=2end with exit subbug:bug("insertPageBreak") End Sub FunctionpasteBlock(oDocas object,oCuras object,dataas object)'vložit blok na konec dokumentuon local error gotobugoCur.gotoEnd(false) withoDoc.CurrentController.select(oCur) .insertTransferable(data) end with withoCur.gotoEnd(false) .ParaKeepTogether=falseend with exit functionbug:bug("pasteBlock") End Function SubpasteBlockHalf(oDocas object,pHalf(), ByRefiHalf&,oCuras object)'vložit půlblokon local error gotobugifiHalf>-1then'jsou nějaké půlblokydimdataas objectoCur.gotoEnd(false)data=pHalf(iHalf)(1) withoDoc.CurrentController.select(oCur) .insertTransferable(data) end with withoCur.gotoEnd(false) .ParaKeepTogether=falseend withiHalf=iHalf-1end if exit subbug:bug("pasteBlockHalf") End Sub FunctionpasteDashedLine(oDocas object,min&,oCuras object,oVCuras object, optionaliHalf&) as boolean'vložit buď Čárkovanou čáru nebo Zalomení stránkyon local error gotobugiNO=iNO+1ifDEBUGthen'DEBUGOVÁNÍ: přidat číslo před Čárkovanou čáruwithoCur.gotoStartOfParagraph(false) .CharHeight=iDASHEDHEIGHT-1.String=iNO.gotoEndOfParagraph(false) end with end if dimiSpace&,bBreakas boolean,iPageHeight&,oPageStyleas objectoCur.gotoEnd(false)oVCur.gotoRange(oCur.End,false)iSpace=getSpace(oDoc,oVCur)'prázdné místo na stránceifiSpace<(iDASHED+min) then'Zalomení stránkyoDoc.Text.insertControlCharacter(oCur.End,com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK,true)'vložit EnterwithoCur.BreakType=com.sun.star.style.BreakType.PAGE_BEFORE.gotoEnd(false)'kurzor na novou stránku.ParaTopMargin=0'ne horní okrajend withbBreak=trueelse'Čárkovaná čáraoPageStyle=oDoc.StyleFamilies.getByName(sPAGESTYLES).getByName(oVCur.PageStyleName)'aktuální Styl stránky' iPageHeight=iHEIGHT - oPageStyle.BottomMargin - oPageStyle.TopMargin 'page height without vertical marginsiPageHeight=iHEIGHTED' výška stránky bez svislých okrajůifiSpace>=iPageHeightthen'kurzor je na začátku nové stránky tudíž nevkládat Čárkovanou čáruwithoCur'vložit Zalomení stránky.gotoEnd(false) .BreakType=com.sun.star.style.BreakType.PAGE_BEFOREend with if NOTisMissing(iHalf) then'test na půlblokifiHalf>-1thenbBreak=true'je možné vložit nový půlblokend if else'vložit Čárkovanou čáruwithoDoc.CurrentController.select(oCur) .insertTransferable(oDASHED) end with end if end ifpasteDashedLine=bBreakexit functionbug:bug("pasteDashedLine") End Function SubpasteDashedLine2(oDocas object,oCuras object)'vložit Čárkovanou čáru na pozici oCuron local error gotobugiNO=iNO+1ifDEBUGthen'DEBUGOVÁNÍ: přidat číslo k Čárkované čářewithoCur.gotoEnd(false) .CharHeight=iDASHEDHEIGHT-1.String=iNO.gotoEnd(false) end with end ifoCur.gotoEnd(false) withoDoc.CurrentController.select(oCur) .insertTransferable(oDASHED) end with exit subbug:bug("pasteDashedLine2") End Sub Subproblem(oDocas object,s$, optionali%, optionalsTitle$)'nějaký problém v dokumentuifisMissing(i) theni=0ifisMissing(sTitle) thensTitle=sNULLremoveLock(oDoc)msgbox(s,i,sTitle) stop End Sub SubremoveKeepTogether(oDocas object)'odstranit vlastnost "Spojit s následujícím odstavcem" a nulové Horní okrajeon local error gotobugdimoEnumas object,oas objectoEnum=oDoc.Text.createEnumerationdo whileoEnum.hasMoreElementso=oEnum.nextElementifo.supportsService("com.sun.star.text.Paragraph") then'paragraphoDoc.CurrentController.select(o) witho.ParaKeepTogether=false'žádné "Spojit s následujícím odstavcem".ParaSplit=true'povolit rozdělení odstavce přes stránky.ParaOrphans=0.ParaWidows=0.ParaTopMargin=0'nenulový horní okraj může dělat problémy neboť vkládaný blok jej mít nemusí a jak se to zmixuje tak to nesedíend with elseifo.supportsService("com.sun.star.text.TextTable") then'tabulkawitho.KeepTogether=false'žádné "Spojit s následujícím odstavcem".Split=true'povolit rozdělení přes stránkyend with end if loop exit subbug:bug("removeKeepTogether") End Sub SubremoveLock(oDocas object)'odstraní zamčení a zneviditelnění okna dokumentuon local error gotobugdimundoMgras object,oStatusbaras objectundoMgr=oDoc.UndoManageroStatusbar=oDoc.CurrentController.StatusIndicatorifoDoc.hasControllersLockedthenoDoc.unlockControllersif NOToDoc.CurrentController.Frame.ComponentWindow.isVisiblethenoDoc.CurrentController.Frame.ComponentWindow.Visible=trueifundoMgr.isLockedthenundoMgr.unlockwithoStatusbar.end .resetend with exit subbug:bug("removeLock") End Sub FunctionsortHalfBlocks(pHalf()) asarray'seřadit půlblokyon local error gotobugdimmin&,max&,i&,iSize&,p(iMINHALFtoiMAXHALF),itemas variant,arr(),iCount&,ibound&,j&,oas variantmin=iHEIGHTj=ubound(pHalf) dimpHalf2(j) fori=lbound(pHalf) tojiSize=pHalf(i)(0)'velikost blokuitem=p(iSize) ifTypeName(item)=sVARIANTthen'přidat .getTransferable() objektu do větveiCount=p(iSize)(0)arr=p(iSize)(1)extendArray(arr,iCount)o=pHalf(i)(1)'objekt s .getTransferable()arr(iCount)=op(iSize)(0)=iCount+1p(iSize)(1)=arrelse'nová větev ve stromuredimarr(iREDIM)o=pHalf(i)(1)arr(0)=op(iSize)=array(1,arr) end if nextrem projít pole a vybrat jen větvedimk& fori=lbound(p) toubound(p)item=p(i) ifTypeName(item)=sVARIANTtheniCount=item(0) forj=0toiCount-1pHalf2(k)=array(i,item(1)(j))k=k+1nextjend if nextipHalf=pHalf2exit functionbug:bug("sortHalfBlocks") End Function