Problem z funkcją printToPDF

0

Witajcie,

otóż korzystam z QT 5.9.2. Aby korzystać z dobrodziejstw QtWebEngine - przeniosłem się na kompilator MSVC 2017 64 bit. Mam problem z poprawnym działaniem funkcji. Patrzyłem już na zagadnienia z sieci. Ale jestem troszkę już zdesperowany ponieważ mimo moich usilnych prób - nie mam efektów takich jakich bym chciał.

Generuję w programie plik html. Ten plik HTML - korzysta z stylesheet.css. Zadaniem programu, oprócz generowania - jest również zapis tego pliku HTML do pliku PDF. Wcześniej realizowałem to w zły sposób ponieważ generował mi się plik z zawartością HTML ale bez stylów. Postanowiłem użyć QtWebEngine aby osiągnąć lepsze efekty - samego wyświetlania strony (jednakże nie jest to wymagane) oraz generowania pliku PDF. Używam do tego funkcji printToPdf.

Robię to w ten sposób:

W pliku mainwindow.h deklaruję sobie dwie zmienne:

QWebEngineView *webView = new QWebEngineView;
QString fileName;

A w pliku mainwindow.cpp przy funkcji obsługującej kliknięcie przycisku z mojej formatki:

    // wczytuję nazwę pliku do zapisu w pdf [ output ]
    fileName = QFileDialog::getSaveFileName(0,
                                                    tr("Save pdf"),
                                                    ".",
                                                    tr("PDF Files (*.pdf)"));
    if (fileName.isEmpty()) {
        return;
    }
  
    // wczytuję plik html który [ input ]
    const QString htmlFile = QFileDialog::getOpenFileName(this,"Otwórz html",".",tr("HTML Files (*.html)"));
    
    // ładuję plik do webView 
    webView->load( QUrl::fromLocalFile(QFileInfo(htmlFile).absoluteFilePath()));
    
    // łączę sygnały z slotem który odpali się gdy ładowanie zostanie zakończone
    connect (webView,SIGNAL(loadFinished(bool)), this,SLOT(savePDF(bool)));
    
     // tutaj do testów korzystam z sygnału który poinformuje mnie gdy gdy towrzenie pliku PDF zostanie zakończone.
    connect (webView->page(), SIGNAL(pdfPrintingFinished(QString,bool)), this, SLOT(finishPDF(QString,bool)));*/

A to są kody moich slotów które generują plik dalej

void MainWindow::savePDF(bool ok)
{

    qDebug() << "Załadowano stronę do webView";
    if(ok)
    {
        qDebug() << "ok true";
        webView->page()->printToPdf(fileName);
    }
}

void MainWindow::finishPDF(QString path, bool ok)
{
    qDebug() << "Skończyłem tworzyć PDF-a";
}

Program się kompiluje. Zawartość oczywiście jest większa, ale przedstawiony kod jest kodem testowym. Klikam przycisk. Wybieram plik do zapisu. Wybieram plik html. I wtedy otrzymuję logi. I gdy już ma się pojawić plik PDF - otrzymuję coś takiego:

LINK DO OBRAZKA

Kompletnie nie wiem co zrobić. Wcześniej miałem kłopoty z samym użyciem QtWebEngine - ale zainstalowałem sobie kompilatory i debbuger od Microsoftu. Utworzyłem zestaw narzędzi i działa. Ale pojawił się kolejny problem. Kompletnie nie wiem jak sobie z tym poradzić. Obojętnie co zrobię z komunikatem - plik się nie tworzy.

Proszę o pomoc albo sugestię.

Z góry dzięki

0

Pod kompilator mingw nawet nie ma tego modułu w wersji 5.9.3.
Sprawdź przykład html2topdf: https://doc.qt.io/qt-5.10/qtwebengine-webenginewidgets-html2pdf-html2pdf-cpp.html.
Może mają jakiegoś buga?

0

Jakbym chciał używać pod mingw to musiałbym skompilować samodzielnie "starą" wersję, która nazywa się (o ile się nie mylę) - qtwebkit. Ale od wersji chyba 5.8 pojawił się już QtWebEngine. Ale aby z niego korzystać trzeba kompilować pod MSVC. Pewnie można pod mingw ale chyba lepiej się to na Linuxie robi. Ale nie o tym rzecz idzie.

Patrzyłem na ten przykład. Skompilowałem go u siebie - kompilacja się udała. Ale użycie programu wywalało błąd... z tym, że na pewno nie taki jak ten mój i dotyczył czegoś zupełnie z tym nie związanego. Chyba... bo już tak leciałem w ferworze walki, że nie pamiętam.

Ogólnie gdzieś obczaiłem sugestię, że to jakiś wyciek pamięci może być. Ten błąd. Ale kto wie. To w sumie jest ostatnia rzecz jaka mi została. I zawsze musi coś nie działać. Chyba, że istnieje inny sposób na przetworzenie htmla na pdf z użyciem stylesheet.css. Bo przez podstawianie kodu bezpośrednio do QPrinter - wychodzi bez stylów, css-a. Walczę i nie odpuszczam. Jestem już z programem na serio przy końcu.

Mała edycja:
Dodam, że na podglądzie np. webView->show() czy też jeśli umieszczę webView na formatce - to podgląd strony ładuje się jak najbardziej okej. Tylko ta funkcja printToPdf - tam lecą te błędy.

0

Może da się tą funkcję czymś zastąpić, a jak nie to musisz przetrzepać internet i wyszukać czemu się wywala.

1
  • Tak dla spokojności sprawdzałeś co zwraca webView->page() i czy to nie jest nullptr ?
  • Próbowałeś drugiej wersji printToPdf (tej z lambdą) ?
0

Przepraszam za brak odzewu ale musiałem odejść od kompa.

Wróciłem i ogarniam. Sprawdziłem czy webView->page() czy nie jest czasem nullptr i nie jest.
W Qt jest prosty mechanizm do debugowania - do logów właściwie qDebug(). Są pewnie inne ale na szybko sprawdziłem tym "mechanizmem" co zwraca webView->page() i zwrócił coś takiego:

QWebEnginePage(0x2182ee7f170)

  • czyli na pewno pusty nie jest. Zresztą tworzę sobie już na początku widget webView i ten wybierany plik html się na tym widgetcie pojawia. Bez problemu. Czyli page() na pewno pusty nie jest.

Spróbowałem również drugiej wersji z lambdą - chociaż przyznam, że czuję iż tej lambdy do końca nie zrozumiałem. W każdym razie na tyle ile zrozumiałem konstrukcję - skleiłem taki kod:

    webView->page()->printToPdf([&htmlFile](QByteArray line){
        QFile file(htmlFile);
            if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
            {
                qDebug() << "Nie moge otworzyć pliku";
                return;
            }
            line = file.readAll();
            file.close();
    });

i to również zwraca dokładnie ten sam błąd z obrazka w głównym poście.

Nie mogę się poddać.

Edycja postu:

Wyciągnąłem jeszcze jakieś logi. Jak wybiorę w komunikacie "Przerwij" oraz zamknę mój program dostaję coś takiego:

[9236:8204:0308/223707.334:FATAL:printer_query.cc(31)] Check failed: !worker_. 
Backtrace:
	GetHandleVerifier [0x00000001817E3601+352865]
	QtWebEngineCore::initialize [0x00000001816DBD61+3297]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x00000001802970F6+64694]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x0000000180297447+65543]
	QtWebEngineCore::FaviconManager::qt_static_metacall [0x000000018425C96A+3054490]
	QtWebEngineCore::FaviconManager::qt_static_metacall [0x000000018425CA33+3054691]
	QtWebEngineCore::UserResourceControllerHost::webContentsDestroyed [0x0000000180241D45+51621]
	QtWebEngineCore::UserResourceControllerHost::webContentsDestroyed [0x0000000180241D8A+51690]
	QtWebEngineCore::FaviconManager::qt_static_metacall [0x0000000184072901+1047345]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029C8E7+87207]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029BE2A+84458]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029BE7D+84541]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029BE4D+84493]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029B10B+81099]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029B14D+81165]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029D80F+91087]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029DBF1+92081]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029C38F+85839]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029C967+87335]
	QtWebEngineCore::BrowserContextAdapter::httpCacheMaxSize [0x0000000182B078A9+3173401]
	QtWebEngineCore::FaviconManager::qt_static_metacall [0x00000001841C08B3+2415331]
	QtWebEngineCore::FaviconManager::qt_static_metacall [0x00000001842F2FE5+3670549]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018060C29A+3690074]
	QtWebEngineCore::CookieMonsterDelegateQt::hasCookieMonster [0x0000000182758F81+9682321]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029C319+85721]
	QtWebEngineCore::JavaScriptDialogController::qt_static_metacall [0x000000018029C927+87271]
	QtWebEngineCore::BrowserContextAdapter::httpCacheMaxSize [0x0000000182E3723F+6514095]
	QtWebEngineCore::UserScript::data [0x00000001810161F1+1863201]
	QtWebEngineCore::WebContentsAdapter::webContents [0x00000001802625C9+30921]
	QWebEngineUrlSchemeHandler::`default constructor closure' [0x00000001801B3FC7+247]
	QWebEngineUrlSchemeHandler::`default constructor closure' [0x00000001801B4255+901]
	QWebEngineUrlSchemeHandler::`default constructor closure' [0x00000001801B42F3+1059]
	QtWebEngineCore::WebContentsAdapter::webContents [0x000000018026268C+31116]
	QtWebEngineCore::WebContentsAdapter::webContents [0x0000000180262F26+33318]
	QSizeF::operator*= [0x0000000050AAD537+4852157]
	QBoxLayout::addStretch [0x00000000515F36B6+525725]
	main [0x00007FF70867480A+138] (c:\qt_proj\generator\main.cpp:12)
	WinMain [0x00007FF7086DA25D+237] (c:\users\qt\work\qt\qtbase\src\winmain\qtmain_win.cpp:104)
	invoke_main [0x00007FF7086D861D+45] (f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:107)
	__scrt_common_main_seh [0x00007FF7086D8517+295] (f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:283)
	__scrt_common_main [0x00007FF7086D83DE+14] (f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:326)
	WinMainCRTStartup [0x00007FF7086D86A9+9] (f:\dd\vctools\crt\vcstartup\src\startup\exe_winmain.cpp:17)
	BaseThreadInitThunk [0x00007FFE28C71FE4+20]
	RtlUserThreadStart [0x00007FFE2B2EEFC1+33]
0

Piszę kolejnego posta, a nie edytuję wiadomość ponieważ uznałem, że rozwiązanie jakie wymyśliłem nie jest rozwiązaniem problemu - ale efekt jest zadowalający. I chcę się nim podzielić.

Ponieważ troszkę goni mnie termin, musiałem wymyślić jakieś TYMCZASOWE rozwiązanie. Postanowiłem użyć osobnego programu do konwertowania html-a do pdfa. Oczywiście w czymś w rodzaju open-source. Znalazłem program wkhtmltopdf ( Tutaj link do programu - projekt otwarty, do tego w QT. Skompilowany ale do ściągnięcia są też źródła. Z tego co się zorientowałem oparty jest o QtWebKit - czyli to czego w moim programie nie używam bo jest zastąpione przez QtWebEngine. Ale działa. I do rzeczy:

  • mam ścieżkę do programu wkhtmltopdf (biorę te pliki gdzie zainstalował się ten program i kopiuję je do folderu z projektem)
  • mam ścieżkę do pliku html ( htmlFile)
  • użytkownik określa miejsce zapisu do pdf - czyli mam ścieżkę do wynikowego PDF-a. (fileName)
  • korzystając z QProcess tworzę proces. Bardzo ważne jest - tworząc wskaźnik do process w konstrukcji new QProcess() używamy wskaźnika this czyli QProcess *process = new QProcess(this);.
  • tworzę polecenie czyli łączę ścieżkę do programu + htmlFile + fileName
  • odpalam proces podając utworzone polecenie/ściężkę.

Prezentuję prosty kod samego urywka tworzenia procesu oraz prostej całej funkcji z wczytywaniem pliku:

Tworzenie procesu (razem z qDebug()):

    QProcess *process = new QProcess(this);
    QString file =  "wkhtmltopdf/bin/wkhtmltopdf.exe " + QFileInfo(htmlFile).filePath() + " " + QFileInfo(fileName).filePath();
    qDebug() << "FILE PATH PROCESS = " << file;
    process->start(file);

Cała funkcja:

void MainWindow::on_pushButton_Generate_clicked()
{
    fileName = QFileDialog::getSaveFileName(0, tr("Save pdf"), ".", tr("PDF Files (*.pdf)"));
    if (fileName.isEmpty()) {
        qDebug() << "fileName czyli save pdf jest pusty";
        return;
    }
    const QString htmlFile = QFileDialog::getOpenFileName(this,"Otwórz html",".",tr("HTML Files (*.html)"));
    if (htmlFile.isEmpty()) {
        qDebug() << "htmlFile czyli źródło html jest puste";
        return;
    }

    QProcess *process = new QProcess(this);
    QString file =  "wkhtmltopdf/bin/wkhtmltopdf.exe " + QFileInfo(htmlFile).filePath() + " " + QFileInfo(fileName).filePath();
    qDebug() << "FILE PATH PROCESS = " << file;
    process->start(file);
}

Całość nie rozwiązuje mojego problemu ale jak najbardziej jest efektywne. Mam to co chciałem. To znaczy przerobię to pod mój program - tak aby działo się to z jak najmniejszym angażowaniem użytkownika - jednakże ten wygenerowany pdf - będzie.

Słówko jeszcze o nierozwiązanym problemie. Dalej nie wiem gdzie leży problem. Wydaje mi się, że po stronie kodu nie. Być może kłopot jest bug-iem środowiska, albo (co bardziej podejrzewam) konfiguracją mojego systemu. Gdzieś może nie pasują mu komponenty Microsoft Visual C++ 2010 Redistributable Package lub inne rzeczy - podczas korzystania z QtWebEngine. Będę chciał spróbować ściągnąć nową wersje Qt plus komponent do niej QtWebEngine. Jeśli to nie pomoże, to kompilacja na innym komputerze. Mógłbym jeszcze odinstalować wszystkie pakiety Microsoft Visual C++ - ale wątpię aby to pomogło. Wiem, że są to dość proste pomysły - ale jestem już troszkę zdesperowany i zirytowany. Przynajmniej chcę znaleźć zalążek kłopotu, albo gdzie go konkretnie szukać.

Główny temat pozostawiam otwarty - bo jednak mój problem NIE ZOSTAŁ ROZWIĄZANY - będę walczył, a może też ktoś będzie walczył. Albo ktoś zna rozwiązanie i się podzieli. Albo ma jakieś inne cenne sugestie. Warto się tym podzielić.

Bardzo dziękuję za wszelakie dotychczasowe próby pomocy :) i mam nadzieję - będziemy myśleć dalej. No bo nie można się poddać!

1

Próbowałeś kompilować release zamiast debug: https://stackoverflow.com/questions/48485276/qt-webengine-debug-version-crash

1
TerminusUnknow napisał(a):

Spróbowałem również drugiej wersji z lambdą - chociaż przyznam, że czuję iż tej lambdy do końca nie zrozumiałem. W każdym razie na tyle ile zrozumiałem konstrukcję - skleiłem taki kod:

    webView->page()->printToPdf([&htmlFile](QByteArray line){
        QFile file(htmlFile);
            if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
            {
                qDebug() << "Nie moge otworzyć pliku";
                return;
            }
            line = file.readAll();
            file.close();
    });

Ogółem to powinno być odwrotnie, bo w argumencie dostajesz zawartość pdf jako QByteArray i ją zapisujesz do pliku. No ale, tak czy siak nie to jest przyczyną błędu.

TerminusUnknow napisał(a):

Wyciągnąłem jeszcze jakieś logi. Jak wybiorę w komunikacie "Przerwij" oraz zamknę mój program dostaję coś takiego:

[9236:8204:0308/223707.334:FATAL:printer_query.cc(31)] Check failed: !worker_. 

Asercja z destruktora PrinterQuery. Tak jakby niszczenie tego obiektu było wołane zbyt szybko.

PrinterQuery::~PrinterQuery() {
  // The job should be finished (or at least canceled) when it is destroyed.
  DCHECK(!is_print_dialog_box_shown_);
  // If this fires, it is that this pending printer context has leaked.
  DCHECK(!worker_);     // <- TUTAJ
}

Mógłbyś pokazać cały kod?

0

Przepraszam za brak odzewu - ale wczoraj miałem ciężki dzień pod względem znalezienia czasu na cokolwiek.

czaffik napisał(a):

Próbowałeś kompilować release zamiast debug: https://stackoverflow.com/questions/48485276/qt-webengine-debug-version-crash

@czaffik - DZIAŁA! Kompilacja na Release działa jak najbardziej. Aż dziwię się sobie, że nie spróbowałem tego zrobić wcześniej. Przecież to akurat w wypadku QT jeden przełącznik. Ale się wczoraj ucieszyłem. Byłem na wyjeździe - ale widziałem odpowiedź. Miałem ze sobą swojego kompa i w wolnej chwili na szybko spróbowałem. I działanie bez zarzutu. Jak najbardziej okej. Oczywiście zastanawiające jest to dlaczego w Debug są takie błędy. Szacuneczek za wypatrzenie rozwiązania. Porzucam oczywiście moje tymczasowe rozwiązanie.

tajny_agent napisał(a):

Mógłbyś pokazać cały kod?

Cały kod programu raczej tutaj potrzebny nie jest - bo do testów przerobiłem sobie tak, że sam podaję dla pewności ścieżki do plików po kliknięciu przycisku. Zresztą cały program zajmuje się pobieraniem i przetwarzaniem danych - potem generuje sobie htmla. Dopiero na końcu przetwarza go na PDF-a. Na pewno zapodam cały kod związany z zamianą html na PDF:

Tutaj z mainwindow.h

    QWebEngineView *webView;
    QString fileName;

oraz plik mainwindow.cpp - tutaj część konstruktora:

    webView = new QWebEngineView(this);
    webView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    ui->centralWidget->layout()->addWidget(webView);

a tutaj procedury kliknięcia przycisku oraz sloty

void MainWindow::on_pushButton_Generate_clicked()
{
    fileName = QFileDialog::getSaveFileName(0, tr("Save pdf"), ".", tr("PDF Files (*.pdf)"));
    if (fileName.isEmpty()) {
        qDebug() << "fileName czyli save pdf jest pusty";
        return;
    }
    connect (webView->page(),SIGNAL(loadFinished(bool)), this,SLOT(savePDF(bool)));
    const QString htmlFile = QFileDialog::getOpenFileName(this,"Otwórz html",".",tr("HTML Files (*.html)"));
    webView->load( QUrl::fromLocalFile(QFileInfo(htmlFile).filePath()));


    qDebug() << webView->page();
    if(webView->page() == nullptr )
        qDebug() << "Racja! nullptr";

    /*QProcess *process = new QProcess(this);
    QString file =  "wkhtmltopdf/bin/wkhtmltopdf.exe " + QFileInfo(htmlFile).filePath() + " " + QFileInfo(fileName).filePath();
    qDebug() << "FILE PATH PROCESS = " << file;
    process->start(file);*/
}

void MainWindow::savePDF(bool ok)
{

    qDebug() << "Załadowano stronę do webView";
    if(ok)
    {
       qDebug() << "ok true";
       webView->page()->printToPdf(fileName);
    }
}

Sorka za moje skromne qDebug() - ale to do testów.

Problem w sumie jest rozwiązany i nie jest. Bo wystarczy przenieść się na release i będzie okej. Ale w debug działać nie będzie. Dziwię się troszkę, że problem może leżeć przy niszczeniu obiektu. W końcu metoda samego konwertowania do PDF-a jest w slocie który jest uruchamiany dopiero po upewnieniu się, że plik jest załadowany. Więc skoro jest załadowany, to może generować PDF-a, a potem sobie go zniszczyć. Ale przyznam, że to na pewno dobry trop - bo gdy w debug wykrzaczała mi się ta funkcja to podgląd na widgecie webView po błędzie znikał. Czyli faktycznie obiekt był jakby zbyt szybko niszczony. Czy przerobienie tego destruktora w odpowiedni sposób rozwiązałoby problem? Czy trzeba skompilować cały QtWebEngine od nowa? Dodatkowo dodam, że jak już pisałem - błąd który mi się pojawiał - najczęściej wynika z wycieku pamięci - a skoro piszą tam w destruktorze PrintQuery - frazę: "If this fires, it is that this pending printer context has leaked." - to znaczy, że jest wyciek. Ale jak prawem skoro wszystko jest poprawnie ładowane i odpala się drukowanie do PDF-a po upewnieniu się, że faktycznie ładowanie pliku się skończyło. Być może to jest bug qt... nie wiem.

Ale się duuuużo nauczyłem! Ludzie dziękuję! Za zainteresowanie, badanie tematu i pomoc. Oczywiście już spokojny w tym, że mój program działa. Dopieszczę go sobie - ale na pewno będę starał się problem poprawnego działania w debug - rozwiązać.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.