Jak znovu spojit rozsekaný text v editoru PDF

turek

Jak znovu spojit rozsekaný text v editoru PDF
« kdy: 04. 04. 2017, 22:50:21 »
Mám takový problém s PDF soubory :  Normálně v prohlížeči PDF (Sumatra, Foxit, Chrome, Čtečka Windows) je text možné vybrat v pořádku jako celek.
Ale když potřebuji dělat úpravy v PDF a mám ho otevřený v nějakém editoru PDF (Libreoffice Draw, CorelDraw), tak odstavcový text je rozsekán na  textová pole o velikosti řádku nebo v horším případě části řádků. Pak je libové v tom něco dělat... Jaktože třeba prohlížeče při výběru textu poznají správné pořadí bloků textu, že vybraný text je ve správném pořadí? Je tam nějaký algoritmus , co sleduje  plynutí textu doprava a adolů , nebo je návaznost bloků explicitně v PDF zaznamenána?

Zajímalo co je toho loket, a jak donutit ty editory, aby pracovaly s textem "spojitě". Mám trochu tušení, že je to nutné kvůli tomu, když je použita pokročilá sazba jako například, když třeba píšu text zarovnaný do bloku, tak se musí trochu manipulovat s mezery mezi slovy a písmeny, aby text byl fakt do bloku, když zrovna nepoužiju rozdělení slov (ono to samozřejmě dělá i když slova rozdělením (volitelným) spojovníkem).


Ale ono to i takhle parceluje  text (dělí to i v rámci řádku), když je použito  primitivní defaultní zarovnání vlevo.

Jisté řešení je text označit v PDF vieweru a překopírovat do editoru...Ale má mouchy (formátování), je pracné (výběr spoust textových objektů, následně smazat,) a samozřejmě konceptuální, proč když to umí viewer PDF, to neumí editor?
« Poslední změna: 04. 04. 2017, 23:43:19 od Petr Krčmář »


JardaP .

  • *****
  • 11 064
    • Zobrazit profil
    • E-mail
Re:Jak znovu-spojit rosekaný text v editoru PDF
« Odpověď #1 kdy: 04. 04. 2017, 23:13:10 »
Pokud vim, v PDF neni jak poznat, co je konec odstavce. Muzete to leda konvertovat do textu nebo html nebo neceho a pak si zbastlit skript (Libreoffice makro), ktere nadela odstavce z bloku textu, ktere jsou oddelene vyraznejsi mezerou a pak to dobastlit rucne, protoze v tom urcite bude rada chyb tam, kde je nejak jine formatovani. PDF nebyl na editaci vubec delany. Je to write only, takze editace muze tezko byt jina, nez pekny opruz.

Re:Jak znovu spojit rozsekaný text v editoru PDF
« Odpověď #2 kdy: 05. 04. 2017, 00:25:12 »
Přesně tak.

Je to vnitřně dost složité a když to zjednoduším, tak se editaci pdf pokud možno vyhněte, opravdu to na to není určené (ale pdf na druhou stranu má spoustu jiných zajímavých možností, skriptování, formuláře, anotace, embedování dat, odklazy atd.).

Ono totiž hodně záleží na tom, jak pdf vzniklo. Z některých pdf text vůbec nedostanete a jinde to zase může být docela spolehlivé. Osobně text z pdf tahám pomocí pdftotext - dostanete čistý text který je potřeba často ještě opravit.

pb

Re:Jak znovu spojit rozsekaný text v editoru PDF
« Odpověď #3 kdy: 05. 04. 2017, 07:53:16 »
Jestli si k tomu chcete něco nastudovat, tak pdfminer (knihovna pro python) je dobrý začátek. Kdysi jsem se pomocí ní pokoušel udělat konverzi z pdf na "rozumnou" tabulku, ale nedotáhl...

V pdfku je (v terminologii pdfmineru) LTTextBox, v něm je LTTextLine, a v něm jsou LTChar. Každý z těchto uzlů může mít souřadnice, a jestli se nepletu, tak jsou relativní vůči souřadnicím rodiče. Je asi dost spolehlivé věřit tomu, že když poskládáte LTChary za sebe v rámci LTTextLine, tak dostanete řádku. Když poskládáte LTTextLine za sebe v rámci LTTextBox, tak dostanete odstavec. Nevím, jestli lze mít LTTextBox a v něm rovnou LTChar a zalamuje si to samo. Asi by šlo tu strukturu přefiltrovat, zrušit souřadnice u LTChar, případně ty, co jsou na jednom řádku squashnout. Jestli to chcete moc, napíšu Vám to, ale bez záruky.

Jak už psali ostatní záleží na pdfku. Jestli jsou všechna generovaná stejným způsobem, tak by to mohlo jít...

Ivan Nový

Re:Jak znovu spojit rozsekaný text v editoru PDF
« Odpověď #4 kdy: 05. 04. 2017, 08:27:14 »
Jestli si k tomu chcete něco nastudovat, tak pdfminer (knihovna pro python) je dobrý začátek. Kdysi jsem se pomocí ní pokoušel udělat konverzi z pdf na "rozumnou" tabulku, ale nedotáhl...

V pdfku je (v terminologii pdfmineru) LTTextBox, v něm je LTTextLine, a v něm jsou LTChar. Každý z těchto uzlů může mít souřadnice, a jestli se nepletu, tak jsou relativní vůči souřadnicím rodiče. Je asi dost spolehlivé věřit tomu, že když poskládáte LTChary za sebe v rámci LTTextLine, tak dostanete řádku. Když poskládáte LTTextLine za sebe v rámci LTTextBox, tak dostanete odstavec. Nevím, jestli lze mít LTTextBox a v něm rovnou LTChar a zalamuje si to samo. Asi by šlo tu strukturu přefiltrovat, zrušit souřadnice u LTChar, případně ty, co jsou na jednom řádku squashnout. Jestli to chcete moc, napíšu Vám to, ale bez záruky.

Jak už psali ostatní záleží na pdfku. Jestli jsou všechna generovaná stejným způsobem, tak by to mohlo jít...
Kdysi jsem dělal něco podobného, možná vám to pomůže, vybere text z PDF pomocí knihovny pdfminer. Uloží text do xml tvaru

Kód: [Vybrat]
  <table>
      <row row="1">
         <cell row="1" col="1">...</cell>
      </row>
  </table>


Problém je v tom, že bloky textu mohou být ve stránce pozicovány různě, na pixely a pořadí bloků neodpovídá pořadí ve stránce, nebo
to pořadí závisí na interpretaci čtenáře. Proto jsem to řešil převodem xml s výše uvedenou strukturou, ze které se už dá poskládat cokoliv. Následující kód realizuje vybrání textu.

Kód: [Vybrat]
    class _TextExtractDevice(PDFDevice):
        """Vybere text z dokumentu"""

        row = 0

        class _EncodedStringIO(StringIO):
            """Rozšíření standrnídho řetězcového proudu"""

            encoding = 'utf-8'

            def __init__(self, encoding='utf-8'):
                """Konstruktor"""
                super().__init__()
                self.encoding = encoding

        def __init__(self, resource_manager, encoding='utf-8'):
            """Konstruktor"""
            super().__init__(resource_manager)
            self._encoding = encoding
            self._output = self._EncodedStringIO(encoding)
            self._page = 0
            self._rows = {}
            self._cols = {}

        def close(self):
            """Uzavření proudů"""
            super().close()
            self._output.close()

        def getvalue(self):
            """Vrátí text"""
            return '<?xml version="1.0" encoding="UTF-8"?>\r\n<pdf>\r\n' \
                   + self._output.getvalue() \
                   + '</pdf>'

        def begin_page(self, page, ctm):
            """Začátek stránky"""
            self._page += 1
            self._output.write('\t<page id="{id}">\r\n'.format(id=self._page))
            self._output.write('\t\t<row>\r\n')
            self._rows = {}
            self._cols = {}

        def end_page(self, page):
            """Konec stránky"""
            self._output.write('\t\t</row>\r\n')
            self._output.write('\t</page>\r\n')

        def render_string(self, textstate, seq):
            """Zpracuje řetězec"""

            # Přidá označení řádku a sloupce do slovníku a vrátí číslo řádku
            col = self._transform_col(textstate.matrix[4])
            row = self._transform_row(textstate.matrix[5])

            # Sestaví frázi
            frase = ''.join([self._render(cell, textstate) for cell in seq if isinstance(cell, str)]).strip()

            # Zapíše frázi na výstup
            if len(frase.strip()) > 0:

                # Test nového řádku
                if row > self.row:
                    self.row = row
                    print_row = True
                else:
                    print_row = False

                # Označení řádku
                if print_row:

                    # Konec předchozího řádku
                    if self.row != 0:
                        self._output.write('\t\t</row>\r\n')

                    # Začátek řádku
                    self._output.write('\t\t<row row="{row}">\r\n'.format(row=row))

                # Buňky
                self._output.write('\t\t\t<cell row="{row}" col="{col}">'.format(row=row, col=col))
                self._output.write(frase)
                self._output.write('</cell>\r\n')

            # Uchová pozici aktuálního řádku
            self.row = row

        def _render(self, input_text, textstate):
            """Správně vykreslí text"""
            return Tools.repeair_for_xml(input_text)

        def _transform_row(self, pos):
            """Přidá označení řádku do slovníku a vrátí číslo řádku"""
            pos = int(pos)
            if pos in self._rows:
                row = self._rows[pos]
            else:
                row = len(sorted(self._rows.keys())) + 1
                self._rows[pos] = row
            return row

        def _transform_col(self, pos):
            """Přidá označení sloupce do slovníku a vrátí číslo sloupce"""
            pos = int(pos)
            if pos in self._cols:
                col = self._cols[pos]
            else:
                col = len(sorted(self._cols.keys())) + 1
                self._cols[pos] = col
            return col


x14

  • ***
  • 182
    • Zobrazit profil
    • E-mail
Re:Jak znovu spojit rozsekaný text v editoru PDF
« Odpověď #5 kdy: 05. 04. 2017, 12:50:31 »
PDF není formát určený k editaci. To, že ho některé programy umí naimportovat a pak vyexportovat, možná vzbuzuje pocit, že jsou to PDF editory, ale to je jen iluze.

Řádek textu je uvnitř PDF uložen takovouto sekcí příkazů:

BT
   /FNT1 9 Tf
   1 0 0 -1 156.258011 288.200012 Tm
   [<0033>12<0050><0050><0055><0001><0053>-8<0056><004D><0046>-4<005B>] TJ
ET

volný překlad

Begin text
   Vyber font se jménem FNT1 o velikosti 9
   Použij matici
      1 0 0 -1 = žádná změna měřítka+rotace+sklon, pouze vzhůru nohama
      156.2 288.2 = absolutní pozice prvního znaku "řádku"
   Sázej znaky <xxx>kk<xxx>kk<xxx>
      xxx jsou většinou glyphindexy - to záleží na definici fontu
      kkk jsou kerningové vzdálenosti mezi znaky (šířky znaků jsou zapsané v definici fontu)
End text

Toto je řádek textu, klidně tam takto může být nabouchané každé jednotlivé písmenko zvlášť.
Řádky mezi sebou nemají žádnou spojitost, tedy kromě toho, že se v zápise většinou vyskytují za sebou a ve výsledném zobrazení většinou pod sebou.