diff options
| author | 2014-12-06 17:33:25 -0500 | |
|---|---|---|
| committer | 2014-12-06 17:33:25 -0500 | |
| commit | 7167ce41b61d2ba2cdb526777a4233eb84a3b66a (patch) | |
| tree | a35c14143716e1f2c98f808c81f89426045a946f /Plugins/PdfExport | |
Imported Upstream version 2.99.6upstream/2.99.6
Diffstat (limited to 'Plugins/PdfExport')
| -rw-r--r-- | Plugins/PdfExport/PdfExport.pro | 26 | ||||
| -rw-r--r-- | Plugins/PdfExport/pdfexport.cpp | 1420 | ||||
| -rw-r--r-- | Plugins/PdfExport/pdfexport.h | 225 | ||||
| -rw-r--r-- | Plugins/PdfExport/pdfexport.json | 8 | ||||
| -rw-r--r-- | Plugins/PdfExport/pdfexport.qrc | 5 | ||||
| -rw-r--r-- | Plugins/PdfExport/pdfexport.ui | 303 | ||||
| -rw-r--r-- | Plugins/PdfExport/pdfexport_global.h | 12 |
7 files changed, 1999 insertions, 0 deletions
diff --git a/Plugins/PdfExport/PdfExport.pro b/Plugins/PdfExport/PdfExport.pro new file mode 100644 index 0000000..46dda37 --- /dev/null +++ b/Plugins/PdfExport/PdfExport.pro @@ -0,0 +1,26 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-06-23T01:06:41 +# +#------------------------------------------------- + +include($$PWD/../../SQLiteStudio3/plugins.pri) + +TARGET = PdfExport +TEMPLATE = lib + +DEFINES += PDFEXPORT_LIBRARY + +SOURCES += pdfexport.cpp + +HEADERS += pdfexport.h\ + pdfexport_global.h + +OTHER_FILES += \ + pdfexport.json + +FORMS += \ + pdfexport.ui + +RESOURCES += \ + pdfexport.qrc diff --git a/Plugins/PdfExport/pdfexport.cpp b/Plugins/PdfExport/pdfexport.cpp new file mode 100644 index 0000000..da15ab7 --- /dev/null +++ b/Plugins/PdfExport/pdfexport.cpp @@ -0,0 +1,1420 @@ +#include "pdfexport.h" +#include "common/unused.h" +#include "uiutils.h" +#include "log.h" +#include <QtMath> +#include <QPainter> +#include <QFont> +#include <QDebug> + +QString PdfExport::bulletChar = "\u2022"; + +bool PdfExport::init() +{ + textOption = new QTextOption(); + textOption->setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + return GenericExportPlugin::init(); +} + +void PdfExport::deinit() +{ + safe_delete(textOption); +} + +QPagedPaintDevice* PdfExport::createPaintDevice(const QString& documentTitle) +{ + QPdfWriter* pdfWriter = new QPdfWriter(output); + pdfWriter->setTitle(documentTitle); + pdfWriter->setCreator(tr("SQLiteStudio v%1").arg(SQLITESTUDIO->getVersionString())); + return pdfWriter; +} + +QString PdfExport::getFormatName() const +{ + return "PDF"; +} + +ExportManager::StandardConfigFlags PdfExport::standardOptionsToEnable() const +{ + return 0; +} + +ExportManager::ExportProviderFlags PdfExport::getProviderFlags() const +{ + return ExportManager::DATA_LENGTHS|ExportManager::ROW_COUNT; +} + +void PdfExport::validateOptions() +{ +} + +QString PdfExport::defaultFileExtension() const +{ + return "pdf"; +} + +bool PdfExport::beforeExportQueryResults(const QString& query, QList<QueryExecutor::ResultColumnPtr>& columns, const QHash<ExportManager::ExportProviderFlag, QVariant> providedData) +{ + UNUSED(query); + + if (!beginDoc(tr("SQL query results"))) + return false; + + totalRows = providedData[ExportManager::ROW_COUNT].toInt(); + + QStringList columnNames; + for (const QueryExecutor::ResultColumnPtr& col : columns) + columnNames << col->displayName; + + clearDataHeaders(); + exportDataColumnsHeader(columnNames); + + QList<int> columnDataLengths = getColumnDataLengths(columnNames.size(), providedData); + calculateDataColumnWidths(columnNames, columnDataLengths); + return true; +} + +bool PdfExport::exportQueryResultsRow(SqlResultsRowPtr row) +{ + exportDataRow(row->valueList()); + return true; +} + +bool PdfExport::exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, const QHash<ExportManager::ExportProviderFlag, QVariant> providedData) +{ + UNUSED(columnNames); + UNUSED(database); + UNUSED(ddl); + + if (isTableExport() && !beginDoc(tr("Exported table: %1").arg(table))) + return false; + + exportObjectHeader(tr("Table: %1").arg(table)); + + QStringList tableDdlColumns = {tr("Column"), tr("Data type"), tr("Constraints")}; + exportObjectColumnsHeader(tableDdlColumns); + + QString colDef; + QString colType; + QStringList columnsAndTypes; + int colNamesLength = 0; + int dataTypeLength = 0; + for (SqliteCreateTable::Column* col : createTable->columns) + { + colDef = col->name; + colNamesLength = qMax(colNamesLength, colDef.size()); + colType = ""; + if (col->type) + { + colType = col->type->toDataType().toFullTypeString(); + colDef += "\n" + colType; + dataTypeLength = qMax(dataTypeLength, colType.size()); + } + + columnsAndTypes << colDef; + } + + QList<int> columnDataLengths = {colNamesLength, dataTypeLength, 0}; + calculateDataColumnWidths(tableDdlColumns, columnDataLengths, 2); + + for (SqliteCreateTable::Column* col : createTable->columns) + exportTableColumnRow(col); + + if (createTable->constraints.size() > 0) + { + QStringList tableDdlColumns = {tr("Global table constraints")}; + exportObjectColumnsHeader(tableDdlColumns); + exportTableConstraintsRow(createTable->constraints); + } + + flushObjectPages(); + + prepareTableDataExport(table, columnsAndTypes, providedData); + return true; +} + +bool PdfExport::exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, const QHash<ExportManager::ExportProviderFlag, QVariant> providedData) +{ + UNUSED(columnNames); + UNUSED(database); + UNUSED(ddl); + UNUSED(createTable); + + if (isTableExport() && !beginDoc(tr("Exported table: %1").arg(table))) + return false; + + prepareTableDataExport(table, columnNames, providedData); + return true; +} + +void PdfExport::prepareTableDataExport(const QString& table, const QStringList& columnNames, const QHash<ExportManager::ExportProviderFlag, QVariant> providedData) +{ + resetDataTable(); + totalRows = providedData[ExportManager::ROW_COUNT].toInt(); + + // Prepare for exporting data row + clearDataHeaders(); + if (!isTableExport()) // for database export we need to mark what is this object name + exportDataHeader(tr("Table: %1").arg(table)); + + exportDataColumnsHeader(columnNames); + + QList<int> columnDataLengths = getColumnDataLengths(columnNames.size(), providedData); + calculateDataColumnWidths(columnNames, columnDataLengths); +} + +QList<int> PdfExport::getColumnDataLengths(int columnCount, const QHash<ExportManager::ExportProviderFlag, QVariant> providedData) +{ + QList<int> columnDataLengths = providedData[ExportManager::DATA_LENGTHS].value<QList<int>>(); + if (columnDataLengths.size() < columnCount) + { + qWarning() << "PdfExport: column widths provided by ExportWorker (" << columnDataLengths.size() + << ") is less than number of columns to export (" << columnCount << ")."; + } + + // Fill up column data widths if there are any missing from the provided data (should not happen) + while (columnDataLengths.size() < columnCount) + columnDataLengths << maxColWidth; + + for (int& val : columnDataLengths) + { + if (val > cellDataLimit) + val = cellDataLimit; + } + + return columnDataLengths; +} + +bool PdfExport::exportTableRow(SqlResultsRowPtr data) +{ + exportDataRow(data->valueList()); + return true; +} + +bool PdfExport::afterExport() +{ + endDoc(); + return true; +} + +bool PdfExport::afterExportTable() +{ + flushDataPages(true); + return true; +} + +bool PdfExport::afterExportQueryResults() +{ + flushDataPages(true); + return true; +} + +bool PdfExport::beforeExportDatabase(const QString& database) +{ + return beginDoc(tr("Exported database: %1").arg(database)); +} + +bool PdfExport::exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex) +{ + UNUSED(database); + UNUSED(ddl); + + exportObjectHeader(tr("Index: %1").arg(name)); + + QStringList indexColumns = {tr("Property", "index header"), tr("Value", "index header")}; + exportObjectColumnsHeader(indexColumns); + + exportObjectRow({tr("Indexed table"), name}); + exportObjectRow({tr("Unique index"), (createIndex->uniqueKw ? tr("Yes") : tr("No"))}); + + indexColumns = {tr("Column"), tr("Collation"), tr("Sort order")}; + exportObjectColumnsHeader(indexColumns); + + QString sort; + for (SqliteIndexedColumn* idxCol : createIndex->indexedColumns) + { + if (idxCol->sortOrder != SqliteSortOrder::null) + sort = sqliteSortOrder(idxCol->sortOrder); + else + sort = ""; + + exportObjectRow({idxCol->name, idxCol->collate, sort}); + } + + if (createIndex->where) + { + indexColumns = {tr("Partial index condition")}; + exportObjectColumnsHeader(indexColumns); + exportObjectRow(createIndex->where->detokenize()); + } + + flushObjectPages(); + return true; +} + +bool PdfExport::exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger) +{ + UNUSED(database); + UNUSED(ddl); + + exportObjectHeader(tr("Trigger: %1").arg(name)); + + QStringList trigColumns = {tr("Property", "trigger header"), tr("Value", "trigger header")}; + exportObjectColumnsHeader(trigColumns); + exportObjectRow({tr("Activation time"), SqliteCreateTrigger::time(createTrigger->eventTime)}); + + QString event = createTrigger->event ? SqliteCreateTrigger::Event::typeToString(createTrigger->event->type) : ""; + exportObjectRow({tr("For action"), event}); + + QString onObj; + if (createTrigger->eventTime == SqliteCreateTrigger::Time::INSTEAD_OF) + onObj = tr("On view"); + else + onObj = tr("On table"); + + exportObjectRow({onObj, createTrigger->table}); + + QString cond = createTrigger->precondition ? createTrigger->precondition->detokenize() : ""; + exportObjectRow({tr("Activation condition"), cond}); + + QStringList queryStrings; + for (SqliteQuery* q : createTrigger->queries) + queryStrings << q->detokenize(); + + exportObjectColumnsHeader({tr("Code executed")}); + exportObjectRow(queryStrings.join("\n")); + + flushObjectPages(); + return true; +} + +bool PdfExport::exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr view) +{ + UNUSED(database); + UNUSED(ddl); + + exportObjectHeader(tr("View: %1").arg(name)); + exportObjectColumnsHeader({tr("Query:")}); + exportObjectRow(view->select->detokenize()); + + flushObjectPages(); + return true; +} + +bool PdfExport::isBinaryData() const +{ + return true; +} + +bool PdfExport::beginDoc(const QString& title) +{ + safe_delete(painter); + safe_delete(pagedWriter); + pagedWriter = createPaintDevice(title); + if (!pagedWriter) + return false; + + painter = new QPainter(pagedWriter); + painter->setBrush(Qt::NoBrush); + painter->setPen(QPen(Qt::black, lineWidth)); + + setupConfig(); + return true; +} + +void PdfExport::endDoc() +{ + drawFooter(); +} + +void PdfExport::cleanupAfterExport() +{ + safe_delete(painter); + safe_delete(pagedWriter); +} + +void PdfExport::setupConfig() +{ + pagedWriter->setPageSize(convertPageSize(cfg.PdfExport.PageSize.get())); + pageWidth = pagedWriter->width(); + pageHeight = pagedWriter->height(); + pointsPerMm = pageWidth / pagedWriter->pageSizeMM().width(); + + stdFont = cfg.PdfExport.Font.get(); + stdFont.setPointSize(cfg.PdfExport.FontSize.get()); + boldFont = stdFont; + boldFont.setBold(true); + italicFont = stdFont; + italicFont.setItalic(true); + painter->setFont(stdFont); + + topMargin = mmToPoints(cfg.PdfExport.TopMargin.get()); + rightMargin = mmToPoints(cfg.PdfExport.RightMargin.get()); + leftMargin = mmToPoints(cfg.PdfExport.LeftMargin.get()); + bottomMargin = mmToPoints(cfg.PdfExport.BottomMargin.get()); + updateMargins(); + + maxColWidth = pageWidth / 5; + padding = mmToPoints(cfg.PdfExport.Padding.get()); + + QRectF rect = painter->boundingRect(QRectF(padding, padding, pageWidth - 2 * padding, 1), "X", *textOption); + minRowHeight = rect.height() + padding * 2; + maxRowHeight = qMax((int)(pageHeight * 0.225), minRowHeight); + rowsToPrebuffer = (int)qCeil((double)pageHeight / minRowHeight); + + cellDataLimit = cfg.PdfExport.MaxCellBytes.get(); + printRowNum = cfg.PdfExport.PrintRowNum.get(); + printPageNumbers = cfg.PdfExport.PrintPageNumbers.get(); + + lastRowY = getContentsTop(); + currentPage = -1; + rowNum = 1; +} + +void PdfExport::updateMargins() +{ + pageWidth -= (leftMargin + rightMargin); + pageHeight -= (topMargin + bottomMargin); + painter->setClipRect(QRect(leftMargin, topMargin, pageWidth, pageHeight)); + + if (printPageNumbers) + { + int pageNumHeight = getPageNumberHeight(); + bottomMargin += pageNumHeight; + pageHeight -= pageNumHeight; + } + + // In order to render full width of the line, we need to add more margin, a half of the line width + leftMargin += lineWidth / 2; + rightMargin += lineWidth / 2; + topMargin += lineWidth / 2; + bottomMargin += lineWidth / 2; + pageWidth -= lineWidth; + pageHeight -= lineWidth; +} + +void PdfExport::clearDataHeaders() +{ + headerRow.reset(); + columnsHeaderRow.reset(); +} + +void PdfExport::resetDataTable() +{ + clearDataHeaders(); + bufferedDataRows.clear(); + rowNum = 0; +} + +void PdfExport::exportDataRow(const QList<QVariant>& data) +{ + DataCell cell; + DataRow row; + + for (const QVariant& value : data) + { + switch (value.type()) + { + case QVariant::Int: + case QVariant::UInt: + case QVariant::LongLong: + case QVariant::ULongLong: + case QVariant::Double: + cell.alignment = Qt::AlignRight; + break; + default: + cell.alignment = Qt::AlignLeft; + break; + } + + if (value.isNull()) + { + cell.alignment = Qt::AlignCenter; + cell.isNull = true; + cell.contents = QStringLiteral("NULL"); + } + else + { + cell.isNull = false; + cell.contents = value.toString(); + } + row.cells << cell; + } + + bufferedDataRows << row; + checkForDataRender(); +} + +void PdfExport::exportObjectHeader(const QString& contents) +{ + ObjectRow row; + ObjectCell cell; + cell.headerBackground = true; + cell.contents << contents; + cell.bold = true; + cell.alignment = Qt::AlignCenter; + row.cells << cell; + + row.type = ObjectRow::Type::SINGLE; + row.recalculateColumnWidths = true; + bufferedObjectRows << row; +} + +void PdfExport::exportObjectColumnsHeader(const QStringList& columns) +{ + ObjectRow row; + ObjectCell cell; + + for (const QString& col : columns) + { + cell.headerBackground = true; + cell.contents.clear(); + cell.contents << col; + cell.alignment = Qt::AlignCenter; + row.cells << cell; + } + + row.recalculateColumnWidths = true; + row.type = ObjectRow::Type::MULTI; + bufferedObjectRows << row; +} + +void PdfExport::exportTableColumnRow(SqliteCreateTable::Column* column) +{ + ObjectRow row; + row.type = ObjectRow::Type::MULTI; + + ObjectCell cell; + cell.contents << column->name; + row.cells << cell; + cell.contents.clear(); + + if (column->type) + cell.contents << column->type->toDataType().toFullTypeString(); + else + cell.contents << ""; + + row.cells << cell; + cell.contents.clear(); + + if (column->constraints.size() > 0) + { + for (SqliteCreateTable::Column::Constraint* constr : column->constraints) + cell.contents << constr->detokenize(); + + cell.type = ObjectCell::Type::LIST; + } + else + { + cell.contents << ""; + } + row.cells << cell; + cell.contents.clear(); + + bufferedObjectRows << row; +} + +void PdfExport::exportTableConstraintsRow(const QList<SqliteCreateTable::Constraint*>& constrList) +{ + ObjectRow row; + row.type = ObjectRow::Type::SINGLE; + + ObjectCell cell; + cell.type = ObjectCell::Type::LIST; + + if (constrList.size() > 0) + { + for (SqliteCreateTable::Constraint* constr : constrList) + cell.contents << constr->detokenize(); + } + else + { + cell.contents << ""; + } + row.cells << cell; + + bufferedObjectRows << row; +} + +void PdfExport::exportObjectRow(const QStringList& values) +{ + ObjectRow row; + row.type = ObjectRow::Type::MULTI; + + ObjectCell cell; + for (const QString& value : values) + { + cell.contents << value; + row.cells << cell; + cell.contents.clear(); + } + + bufferedObjectRows << row; +} + +void PdfExport::exportObjectRow(const QString& value) +{ + ObjectRow row; + row.type = ObjectRow::Type::SINGLE; + + ObjectCell cell; + cell.contents << value; + row.cells << cell; + + bufferedObjectRows << row; +} + +int PdfExport::calculateRowHeight(int maxTextWidth, const QStringList& listContents) +{ + int textWidth = maxTextWidth - calculateBulletPrefixWidth(); + int totalHeight = 0; + for (const QString& contents : listContents) + totalHeight += calculateRowHeight(textWidth, contents); + + return totalHeight; +} + +int PdfExport::calculateBulletPrefixWidth() +{ + static QString prefix = bulletChar + " "; + + QTextOption opt = *textOption; + opt.setWrapMode(QTextOption::NoWrap); + + return painter->boundingRect(QRect(0, 0, 1, 1), prefix, *textOption).width(); +} + +void PdfExport::checkForDataRender() +{ + if (bufferedDataRows.size() >= rowsToPrebuffer) + flushDataPages(); +} + +void PdfExport::flushObjectPages() +{ + if (bufferedObjectRows.isEmpty()) + return; + + int y = getContentsTop(); + int totalHeight = lastRowY - y; + + if (totalHeight > 0) + { + totalHeight += minRowHeight * 2; // a space between objects on one page + y += totalHeight; + } + else + newPage(); + + while (!bufferedObjectRows.isEmpty()) + { + ObjectRow& row = bufferedObjectRows.first(); + + if (row.recalculateColumnWidths || row.cells.size() != calculatedObjectColumnWidths.size()) + calculateObjectColumnWidths(); + + totalHeight += row.height; + if (totalHeight > pageHeight) + { + newPage(); + y = getContentsTop(); + totalHeight = row.height; + } + flushObjectRow(row, y); + + y += row.height; + + bufferedObjectRows.removeFirst(); + } + + lastRowY = getContentsTop() + totalHeight; +} + +void PdfExport::drawObjectTopLine(int y) +{ + painter->drawLine(getContentsLeft(), y, getContentsRight(), y); +} + +void PdfExport::drawObjectCellHeaderBackground(int x1, int y1, int x2, int y2) +{ + painter->save(); + painter->setBrush(QBrush(cfg.PdfExport.HeaderBgColor.get(), Qt::SolidPattern)); + painter->setPen(Qt::NoPen); + painter->drawRect(x1, y1, x2 - x1, y2 - y1); + painter->restore(); +} + +void PdfExport::drawFooter() +{ + QString footer = tr("Document generated with SQLiteStudio v%1").arg(SQLITESTUDIO->getVersionString()); + + QTextOption opt = *textOption; + opt.setAlignment(Qt::AlignRight); + + int y = lastRowY + minRowHeight; + int height = pageHeight - y; + int txtHeight = painter->boundingRect(QRect(0, 0, pageWidth, height), footer, opt).height(); + if ((y + txtHeight) > pageHeight) + { + newPage(); + y = getContentsTop(); + } + + painter->save(); + painter->setFont(italicFont); + painter->drawText(QRect(getContentsLeft(), y, pageWidth, txtHeight), footer, opt); + painter->restore(); +} + +void PdfExport::flushObjectRow(const PdfExport::ObjectRow& row, int y) +{ + painter->save(); + int x = getContentsLeft(); + int bottom = y + row.height; + int top = y; + int left = getContentsLeft(); + int right = getContentsRight(); + switch (row.type) + { + case ObjectRow::Type::SINGLE: + { + const ObjectCell& cell = row.cells.first(); + if (cell.headerBackground) + drawObjectCellHeaderBackground(left, y, right, bottom); + + painter->drawLine(left, y, left, bottom); + painter->drawLine(right, y, right, bottom); + painter->drawLine(left, top, right, top); + painter->drawLine(left, bottom, right, bottom); + + flushObjectCell(cell, left, y, pageWidth, row.height); + break; + } + case ObjectRow::Type::MULTI: + { + int width = 0; + for (int col = 0, total = calculatedObjectColumnWidths.size(); col < total; ++col) + { + width = calculatedObjectColumnWidths[col]; + if (row.cells[col].headerBackground) + drawObjectCellHeaderBackground(x, y, x + width, bottom); + + x += width; + } + + x = left; + painter->drawLine(x, y, x, bottom); + for (int w : calculatedObjectColumnWidths) + { + x += w; + painter->drawLine(x, y, x, bottom); + } + painter->drawLine(left, top, right, top); + painter->drawLine(left, bottom, right, bottom); + + x = left; + for (int col = 0, total = calculatedObjectColumnWidths.size(); col < total; ++col) + { + const ObjectCell& cell = row.cells[col]; + width = calculatedObjectColumnWidths[col]; + flushObjectCell(cell, x, y, width, row.height); + x += width; + } + break; + } + } + painter->restore(); +} + +void PdfExport::flushObjectCell(const PdfExport::ObjectCell& cell, int x, int y, int w, int h) +{ + QTextOption opt = *textOption; + opt.setAlignment(cell.alignment); + + if (cell.bold) + painter->setFont(boldFont); + else if (cell.italic) + painter->setFont(italicFont); + + switch (cell.type) + { + case ObjectCell::Type::NORMAL: + { + painter->drawText(QRect(x + padding, y + padding, w - 2 * padding, h - 2 * padding), cell.contents.first(), opt); + break; + } + case ObjectCell::Type::LIST: + { + static QString prefix = bulletChar + " "; + int prefixWidth = calculateBulletPrefixWidth(); + x += padding; + y += padding; + w -= 2 * padding; + h -= 2 * padding; + int txtX = x + prefixWidth; + int txtW = w - prefixWidth; + + QTextOption prefixOpt = opt; + prefixOpt.setAlignment(opt.alignment() | Qt::AlignTop); + + int txtH = 0; + for (const QString& contents : cell.contents) + { + txtH = calculateRowHeight(txtW, contents); + painter->drawText(QRect(x, y, prefixWidth, txtH), prefix, prefixOpt); + painter->drawText(QRect(txtX, y, txtW, txtH), contents, opt); + y += txtH; + } + break; + } + } +} + +void PdfExport::calculateObjectColumnWidths(int columnToExpand) +{ + calculatedObjectColumnWidths.clear(); + if (bufferedObjectRows.size() == 0) + return; + + QTextOption opt = *textOption; + opt.setWrapMode(QTextOption::NoWrap); + + int colCount = bufferedObjectRows.first().cells.size(); + for (int i = 0; i < colCount; i++) + calculatedObjectColumnWidths << 0; + + int width = 0; + for (const ObjectRow& row : bufferedObjectRows) + { + if (row.cells.size() != colCount) + break; + + for (int col = 0; col < colCount; col++) + { + width = painter->boundingRect(QRectF(0, 0, 1, 1), row.cells[col].contents.join("\n"), opt).width(); + width += 2 * padding; + calculatedObjectColumnWidths[col] = qMax(calculatedObjectColumnWidths[col], width); + } + } + + int totalWidth = correctMaxObjectColumnWidths(colCount, columnToExpand); + if (totalWidth < pageWidth) + { + int col = (columnToExpand > -1) ? columnToExpand : (colCount - 1); + calculatedObjectColumnWidths[col] += (pageWidth - totalWidth); + } + + calculateObjectRowHeights(); +} + +int PdfExport::correctMaxObjectColumnWidths(int colCount, int columnToExpand) +{ + int totalWidth = 0; + for (int w : calculatedObjectColumnWidths) + totalWidth += w; + + int maxWidth = pageWidth / colCount; + if (totalWidth <= pageWidth) + return totalWidth; + + int tmpWidth = 0; + for (int col = 0; col < colCount && totalWidth > pageWidth; col++) + { + if (calculatedObjectColumnWidths[col] <= maxWidth) + continue; + + if (col == columnToExpand) + continue; // will handle that column as last one (if needed) + + tmpWidth = calculatedObjectColumnWidths[col]; + if ((totalWidth - calculatedObjectColumnWidths[col] + maxWidth) <= pageWidth) + { + calculatedObjectColumnWidths[col] -= (pageWidth - totalWidth + calculatedObjectColumnWidths[col] - maxWidth); + return pageWidth; // the 'if' condition guarantees that shrinking this column that much will give us pageWidth + } + else + calculatedObjectColumnWidths[col] = maxWidth; + + totalWidth -= tmpWidth - calculatedObjectColumnWidths[col]; + } + + if (columnToExpand > -1 && totalWidth > pageWidth) + { + tmpWidth = calculatedObjectColumnWidths[columnToExpand]; + if ((totalWidth - calculatedObjectColumnWidths[columnToExpand] + maxWidth) <= pageWidth) + calculatedObjectColumnWidths[columnToExpand] -= (pageWidth - totalWidth + calculatedObjectColumnWidths[columnToExpand] - maxWidth); + else + calculatedObjectColumnWidths[columnToExpand] = maxWidth; + } + + return pageWidth; +} + +void PdfExport::calculateObjectRowHeights() +{ + int maxHeight = 0; + int colWidth = 0; + int height = 0; + int colCount = calculatedObjectColumnWidths.size(); + for (ObjectRow& row : bufferedObjectRows) + { + if (row.cells.size() != colCount) + return; // stop at this row, further calculation will be done when columns are recalculated + + maxHeight = 0; + for (int col = 0; col < colCount; ++col) + { + colWidth = calculatedObjectColumnWidths[col]; + const ObjectCell& cell = row.cells[col]; + + switch (cell.type) + { + case ObjectCell::Type::NORMAL: + height = calculateRowHeight(colWidth, cell.contents.first()); + break; + case ObjectCell::Type::LIST: + height = calculateRowHeight(colWidth, cell.contents); + break; + } + maxHeight = qMax(maxHeight, height); + } + + row.height = qMin(maxRowHeight, maxHeight); + } +} + +void PdfExport::flushDataPages(bool forceRender) +{ + calculateDataRowHeights(); + + int rowsToRender = 0; + int totalRowHeight = 0; + int colStartAt = 0; + while ((bufferedDataRows.size() >= rowsToPrebuffer) || (forceRender && bufferedDataRows.size() > 0)) + { + // Calculate how many rows we can render on single page + rowsToRender = 0; + totalRowHeight = totalHeaderRowsHeight; + for (const DataRow& row : bufferedDataRows) + { + rowsToRender++; + totalRowHeight += row.height; + if (totalRowHeight >= pageHeight) + { + rowsToRender--; + break; + } + } + + // Render limited number of columns and rows per single page + colStartAt = 0; + for (int cols : columnsPerPage) + { + newPage(); + flushDataRowsPage(colStartAt, colStartAt + cols, rowsToRender); + colStartAt += cols; + } + + for (int i = 0; i < rowsToRender; i++) + bufferedDataRows.removeFirst(); + + rowNum += rowsToRender; + } +} + +void PdfExport::flushDataRowsPage(int columnStart, int columnEndBefore, int rowsToRender) +{ + QList<DataRow> allRows; + if (headerRow) + allRows += *headerRow; + + if (columnsHeaderRow) + allRows += *columnsHeaderRow; + + allRows += bufferedDataRows.mid(0, rowsToRender); + + int left = getContentsLeft(); + int right = getContentsRight(); + int top = getContentsTop(); + + // Calculating width of all columns on this page + int totalColumnsWidth = sum(calculatedDataColumnWidths.mid(columnStart, columnEndBefore - columnStart)); + int totalColumnsWidthWithRowId = totalColumnsWidth + rowNumColumnWidth; + + // Calculating height of all rows + int totalRowsHeight = 0; + for (const DataRow& row : allRows) + totalRowsHeight += row.height; + + // Draw header background + int x = getDataColumnsStartX(); + painter->save(); + painter->setBrush(QBrush(cfg.PdfExport.HeaderBgColor.get(), Qt::SolidPattern)); + painter->setPen(Qt::NoPen); + painter->drawRect(QRect(x, top, totalColumnsWidth, totalHeaderRowsHeight)); + painter->restore(); + + // Draw rowNum background + if (printRowNum) + { + painter->save(); + painter->setBrush(QBrush(cfg.PdfExport.HeaderBgColor.get(), Qt::SolidPattern)); + painter->setPen(Qt::NoPen); + painter->drawRect(QRect(left, top, rowNumColumnWidth, totalRowsHeight)); + painter->restore(); + } + + // Draw horizontal lines + int y = top; + int horizontalLineEnd = x + totalColumnsWidth; + painter->drawLine(left, y, horizontalLineEnd, y); + for (const DataRow& row : allRows) + { + y += row.height; + painter->drawLine(left, y, horizontalLineEnd, y); + } + + // Draw dashed horizontal lines if there are more columns on the next page and there is space on the right side + if (columnEndBefore < calculatedDataColumnWidths.size() && horizontalLineEnd < right) + { + y = top; + painter->save(); + QPen pen(Qt::lightGray, lineWidth, Qt::DashLine); + pen.setDashPattern(QVector<qreal>({5.0, 3.0})); + painter->setPen(pen); + painter->drawLine(horizontalLineEnd, y, right, y); + for (const DataRow& row : allRows) + { + y += row.height; + painter->drawLine(horizontalLineEnd, y, right, y); + } + painter->restore(); + } + + // Finding first row to start vertical lines from. It's either a COLUMNS_HEADER, or first data row, after headers. + int verticalLinesStart = top; + if (headerRow) + verticalLinesStart += headerRow->height; + + // Draw vertical lines + x = getDataColumnsStartX(); + painter->drawLine(left, top, left, top + totalRowsHeight); + if (printRowNum) + painter->drawLine(x, verticalLinesStart, x, top + totalRowsHeight); + + for (int col = columnStart; col < columnEndBefore; col++) + { + x += calculatedDataColumnWidths[col]; + painter->drawLine(x, (col+1 == columnEndBefore) ? top : verticalLinesStart, x, top + totalRowsHeight); + } + + // Draw header rows + y = top; + if (headerRow) + flushDataHeaderRow(*headerRow, y, totalColumnsWidthWithRowId, columnStart, columnEndBefore); + + if (columnsHeaderRow) + flushDataHeaderRow(*columnsHeaderRow, y, totalColumnsWidthWithRowId, columnStart, columnEndBefore); + + // Draw data + int localRowNum = rowNum; + for (int rowCounter = 0; rowCounter < rowsToRender && !bufferedDataRows.isEmpty(); rowCounter++) + flushDataRow(bufferedDataRows[rowCounter], y, columnStart, columnEndBefore, localRowNum++); + + lastRowY = y; +} + +void PdfExport::flushDataRow(const DataRow& row, int& y, int columnStart, int columnEndBefore, int localRowNum) +{ + int textWidth = 0; + int textHeight = 0; + int colWidth = 0; + int x = getContentsLeft(); + + y += padding; + if (printRowNum) + { + QTextOption opt = *textOption; + opt.setAlignment(Qt::AlignRight); + + x += padding; + textWidth = rowNumColumnWidth - padding * 2; + textHeight = row.height - padding * 2; + flushDataCell(QRect(x, y, textWidth, textHeight), QString::number(localRowNum), &opt); + x += rowNumColumnWidth - padding; + } + + for (int col = columnStart; col < columnEndBefore; col++) + { + const DataCell& cell = row.cells[col]; + colWidth = calculatedDataColumnWidths[col]; + + x += padding; + textWidth = colWidth - padding * 2; + textHeight = row.height - padding * 2; + flushDataCell(QRect(x, y, textWidth, textHeight), cell); + x += colWidth - padding; + } + y += row.height - padding; +} + +void PdfExport::flushDataCell(const QRect& rect, const PdfExport::DataCell& cell) +{ + QTextOption opt = *textOption; + opt.setAlignment(cell.alignment); + + painter->save(); + if (cell.isNull) + { + painter->setPen(cfg.PdfExport.NullValueColor.get()); + painter->setFont(italicFont); + } + + painter->drawText(rect, cell.contents.left(cellDataLimit), opt); + painter->restore(); +} + +void PdfExport::flushDataCell(const QRect& rect, const QString& contents, QTextOption* opt) +{ + painter->drawText(rect, contents.left(cellDataLimit), *opt); +} + +void PdfExport::flushDataHeaderRow(const PdfExport::DataRow& row, int& y, int totalColsWidth, int columnStart, int columnEndBefore) +{ + QTextOption opt = *textOption; + opt.setAlignment(Qt::AlignHCenter); + int x = getContentsLeft(); + y += padding; + switch (row.type) + { + case DataRow::Type::TOP_HEADER: + { + x += padding; + painter->save(); + painter->setFont(boldFont); + painter->drawText(QRect(x, y, totalColsWidth - 2 * padding, row.height - 2 * padding), row.cells.first().contents, opt); + painter->restore(); + break; + } + case DataRow::Type::COLUMNS_HEADER: + { + if (printRowNum) + { + x += padding; + int textWidth = rowNumColumnWidth - padding * 2; + int textHeight = row.height - padding * 2; + painter->drawText(QRect(x, y, textWidth, textHeight), "#", opt); + x += rowNumColumnWidth - padding; + } + + for (int col = columnStart; col < columnEndBefore; col++) + flushDataHeaderCell(x, y, row, col, &opt); + + break; + } + case DataRow::Type::NORMAL: + break; // no-op + } + y += row.height - padding; +} + +void PdfExport::flushDataHeaderCell(int& x, int y, const PdfExport::DataRow& row, int col, QTextOption* opt) +{ + x += padding; + painter->drawText(QRect(x, y, calculatedDataColumnWidths[col] - 2 * padding, row.height - 2 * padding), row.cells[col].contents, *opt); + x += calculatedDataColumnWidths[col] - padding; +} + +void PdfExport::renderPageNumber() +{ + if (!printPageNumbers) + return; + + QString page = QString::number(currentPage + 1); + + QTextOption opt = *textOption; + opt.setWrapMode(QTextOption::NoWrap); + + painter->save(); + painter->setFont(italicFont); + QRect rect = painter->boundingRect(QRect(0, 0, 1, 1), page, opt).toRect(); + int x = getContentsRight() - rect.width(); + int y = getContentsBottom(); // the bottom margin was already increased to hold page numbers + QRect newRect(x, y, rect.width(), rect.height()); + painter->drawText(newRect, page, *textOption); + painter->restore(); +} + +int PdfExport::getPageNumberHeight() +{ + QTextOption opt = *textOption; + opt.setWrapMode(QTextOption::NoWrap); + + painter->save(); + painter->setFont(italicFont); + int height = painter->boundingRect(QRect(0, 0, 1, 1), "0123456789", opt).height(); + painter->restore(); + return height; +} + +void PdfExport::exportDataHeader(const QString& contents) +{ + DataRow* row = new DataRow; + row->type = DataRow::Type::TOP_HEADER; + + DataCell cell; + cell.contents = contents; + cell.alignment = Qt::AlignHCenter; + row->cells << cell; + + headerRow.reset(row); +} + +void PdfExport::exportDataColumnsHeader(const QStringList& columns) +{ + DataRow* row = new DataRow; + row->type = DataRow::Type::COLUMNS_HEADER; + + DataCell cell; + cell.alignment = Qt::AlignHCenter; + for (const QString& col : columns) + { + cell.contents = col; + row->cells << cell; + } + + columnsHeaderRow.reset(row); +} + +void PdfExport::newPage() +{ + if (currentPage < 0) + { + currentPage = 0; + renderPageNumber(); + return; + } + + pagedWriter->newPage(); + currentPage++; + lastRowY = getContentsTop(); + renderPageNumber(); +} + +void PdfExport::calculateDataColumnWidths(const QStringList& columnNames, const QList<int>& columnDataLengths, int columnToExpand) +{ + static const QString tplChar = QStringLiteral("W"); + + // Text options for calculating widths will not allow word wrapping + QTextOption opt = *textOption; + opt.setWrapMode(QTextOption::NoWrap); + + // Calculate header width first + if (columnToExpand > -1) + { + // If any column was picked for expanding table to page width, the header will also be full page width. + // This will also result later with expanding selected column to the header width = page width. + currentHeaderMinWidth = pageWidth; + } + else + { + currentHeaderMinWidth = 0; + if (headerRow) + { + painter->save(); + painter->setFont(boldFont); + currentHeaderMinWidth = painter->boundingRect(QRectF(0, 0, 1, 1), headerRow->cells.first().contents, opt).width(); + currentHeaderMinWidth += padding * 2; + painter->restore(); + } + } + + // Calculate width of rowNum column (if enabled) + rowNumColumnWidth = 0; + if (printRowNum) + rowNumColumnWidth = painter->boundingRect(QRectF(0, 0, 1, 1), QString::number(totalRows), opt).width() + 2 * padding; + + // Precalculate column widths for the header row + QList<int> headerWidths; + for (const QString& colName : columnNames) + headerWidths << painter->boundingRect(QRectF(0, 0, 1, 1), colName, opt).width(); + + // Calculate width for each column and compare it with its header width, then pick the wider, but never wider than the maximum width. + calculatedDataColumnWidths.clear(); + int dataWidth = 0; + int headerWidth = 0; + int totalWidth = 0; + for (int i = 0, total = columnDataLengths.size(); i < total; ++i) + { + dataWidth = painter->boundingRect(QRectF(0, 0, 1, 1), tplChar.repeated(columnDataLengths[i]), opt).width(); + headerWidth = headerWidths[i]; + + // Pick the wider one, but never wider than maxColWidth + totalWidth = qMax(dataWidth, headerWidth) + padding * 2; // wider one + padding on sides + calculatedDataColumnWidths << qMin(maxColWidth, totalWidth); + } + + // Calculate how many columns will fit for every page, until the full row is rendered. + columnsPerPage.clear(); + int colsForThePage = 0; + int currentTotalWidth = 0; + int expandColumnIndex = 0; + int dataColumnsWidth = getDataColumnsWidth(); + for (int i = 0, total = calculatedDataColumnWidths.size(); i < total; ++i) + { + colsForThePage++; + currentTotalWidth += calculatedDataColumnWidths[i]; + if (currentTotalWidth > dataColumnsWidth) + { + colsForThePage--; + columnsPerPage << colsForThePage; + + // Make sure that columns on previous page are at least as wide as the header + currentTotalWidth -= calculatedDataColumnWidths[i]; + if ((currentTotalWidth + rowNumColumnWidth) < currentHeaderMinWidth && i > 0) + { + expandColumnIndex = 1; + if (columnToExpand > -1) + expandColumnIndex = colsForThePage - columnToExpand; + + calculatedDataColumnWidths[i - expandColumnIndex] += (currentHeaderMinWidth - (currentTotalWidth + rowNumColumnWidth)); + } + + // Reset values fot next interation + currentTotalWidth = calculatedDataColumnWidths[i]; + colsForThePage = 1; + } + } + + if (colsForThePage > 0) + { + columnsPerPage << colsForThePage; + if ((currentTotalWidth + rowNumColumnWidth) < currentHeaderMinWidth && !calculatedDataColumnWidths.isEmpty()) + { + int i = calculatedDataColumnWidths.size(); + expandColumnIndex = 1; + if (columnToExpand > -1) + expandColumnIndex = colsForThePage - columnToExpand; + + calculatedDataColumnWidths[i - expandColumnIndex] += (currentHeaderMinWidth - (currentTotalWidth + rowNumColumnWidth)); + } + } +} + +void PdfExport::calculateDataRowHeights() +{ + // Calculating heights for data rows + int thisRowMaxHeight = 0; + int actualColHeight = 0; + for (DataRow& row : bufferedDataRows) + { + if (row.height > 0) // was calculated in the previous rendering iteration + continue; + + thisRowMaxHeight = 0; + for (int col = 0, total = row.cells.size(); col < total; ++col) + { + // We pass rect, that is as wide as calculated column width and we look how height extends + actualColHeight = calculateRowHeight(calculatedDataColumnWidths[col], row.cells[col].contents); + thisRowMaxHeight = qMax(thisRowMaxHeight, actualColHeight); + } + row.height = qMin(maxRowHeight, thisRowMaxHeight); + } + + // Calculating heights for header rows + totalHeaderRowsHeight = 0; + if (headerRow) + { + painter->save(); + painter->setFont(boldFont); + // Main header can be as wide as page, so that's the rect we pass + actualColHeight = calculateRowHeight(pageWidth, headerRow->cells.first().contents); + + headerRow->height = qMin(maxRowHeight, actualColHeight); + totalHeaderRowsHeight += headerRow->height; + painter->restore(); + } + + if (columnsHeaderRow) + { + thisRowMaxHeight = 0; + for (int col = 0, total = columnsHeaderRow->cells.size(); col < total; ++col) + { + // This is the same as for data rows (see above) + actualColHeight = calculateRowHeight(calculatedDataColumnWidths[col], columnsHeaderRow->cells[col].contents); + thisRowMaxHeight = qMax(thisRowMaxHeight, actualColHeight); + } + + columnsHeaderRow->height = qMin(maxRowHeight, thisRowMaxHeight); + totalHeaderRowsHeight += columnsHeaderRow->height; + } + +} + +int PdfExport::calculateRowHeight(int maxTextWidth, const QString& contents) +{ + // Measures height expanding due to constrained text width, line wrapping and top+bottom padding + return painter->boundingRect(QRect(0, 0, (maxTextWidth - padding * 2), 1), contents, *textOption).height() + padding * 2; +} + +int PdfExport::getDataColumnsWidth() const +{ + if (printRowNum) + return pageWidth - rowNumColumnWidth; + + return pageWidth; +} + +int PdfExport::getDataColumnsStartX() const +{ + if (printRowNum) + return getContentsLeft() + rowNumColumnWidth; + + return getContentsLeft(); +} + +int PdfExport::getContentsLeft() const +{ + return leftMargin; +} + +int PdfExport::getContentsTop() const +{ + return topMargin; +} + +int PdfExport::getContentsRight() const +{ + return getContentsLeft() + pageWidth; +} + +int PdfExport::getContentsBottom() const +{ + return topMargin + pageHeight; +} + +qreal PdfExport::mmToPoints(qreal sizeMM) +{ + return pointsPerMm * sizeMM; +} + +CfgMain* PdfExport::getConfig() +{ + return &cfg; +} + +QString PdfExport::getExportConfigFormName() const +{ + return "PdfExportConfig"; +} + +QFont Cfg::getPdfExportDefaultFont() +{ + QPainter p; + return p.font(); +} + +QStringList Cfg::getPdfPageSizes() +{ + return getAllPageSizes(); +} diff --git a/Plugins/PdfExport/pdfexport.h b/Plugins/PdfExport/pdfexport.h new file mode 100644 index 0000000..9c7cc62 --- /dev/null +++ b/Plugins/PdfExport/pdfexport.h @@ -0,0 +1,225 @@ +#ifndef PDFEXPORT_H +#define PDFEXPORT_H + +#include "pdfexport_global.h" +#include "plugins/genericexportplugin.h" +#include "config_builder.h" +#include <QPdfWriter> +#include <QFont> +#include <QColor> + +class QPagedPaintDevice; +class QPainter; +class QTextOption; +class QFont; + +namespace Cfg +{ + PDFEXPORTSHARED_EXPORT QFont getPdfExportDefaultFont(); + PDFEXPORTSHARED_EXPORT QStringList getPdfPageSizes(); +} + +CFG_CATEGORIES(PdfExportConfig, + CFG_CATEGORY(PdfExport, + CFG_ENTRY(QString, PageSize, "A4") + CFG_ENTRY(QStringList, PageSizes, Cfg::getPdfPageSizes()) + CFG_ENTRY(int, Padding, 1) + CFG_ENTRY(bool, PrintRowNum, true) + CFG_ENTRY(bool, PrintPageNumbers, true) + CFG_ENTRY(int, TopMargin, 20) + CFG_ENTRY(int, RightMargin, 20) + CFG_ENTRY(int, BottomMargin, 20) + CFG_ENTRY(int, LeftMargin, 20) + CFG_ENTRY(int, MaxCellBytes, 100) + CFG_ENTRY(QFont, Font, Cfg::getPdfExportDefaultFont()) + CFG_ENTRY(int, FontSize, 10) + CFG_ENTRY(QColor, HeaderBgColor, QColor(Qt::lightGray)) + CFG_ENTRY(QColor, NullValueColor, QColor(Qt::gray)) + ) +) + +class PDFEXPORTSHARED_EXPORT PdfExport : public GenericExportPlugin +{ + Q_OBJECT + SQLITESTUDIO_PLUGIN("pdfexport.json") + + public: + QString getFormatName() const; + ExportManager::StandardConfigFlags standardOptionsToEnable() const; + ExportManager::ExportProviderFlags getProviderFlags() const; + void validateOptions(); + CfgMain* getConfig(); + QString getExportConfigFormName() const; + QString defaultFileExtension() const; + bool beforeExportQueryResults(const QString& query, QList<QueryExecutor::ResultColumnPtr>& columns, + const QHash<ExportManager::ExportProviderFlag,QVariant> providedData); + bool exportQueryResultsRow(SqlResultsRowPtr row); + bool exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, + const QHash<ExportManager::ExportProviderFlag,QVariant> providedData); + bool exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, + const QHash<ExportManager::ExportProviderFlag,QVariant> providedData); + bool afterExport(); + bool afterExportTable(); + bool afterExportQueryResults(); + bool exportTableRow(SqlResultsRowPtr data); + bool beforeExportDatabase(const QString& database); + bool exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex); + bool exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger); + bool exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr view); + void cleanupAfterExport(); + bool isBinaryData() const; + bool init(); + void deinit(); + + protected: + virtual QPagedPaintDevice* createPaintDevice(const QString& documentTitle); + + int lineWidth = 15; + + private: + struct DataCell + { + QString contents; + Qt::Alignment alignment = Qt::AlignLeft; + bool isNull = false; + bool isRowNum = false; + }; + + struct DataRow + { + enum class Type + { + NORMAL, + TOP_HEADER, + COLUMNS_HEADER + }; + + QList<DataCell> cells; + int height = 0; + Type type = Type::NORMAL; + }; + + struct ObjectCell + { + enum class Type + { + NORMAL, + LIST + }; + + QStringList contents; + Qt::Alignment alignment = Qt::AlignLeft; + bool headerBackground = false; + bool bold = false; + bool italic = false; + Type type = Type::NORMAL; + }; + + struct ObjectRow + { + enum class Type + { + MULTI, + SINGLE + }; + + QList<ObjectCell> cells; + int height = 0; + Type type = Type::SINGLE; + bool recalculateColumnWidths = false; + }; + + void prepareTableDataExport(const QString& table, const QStringList& columnNames, const QHash<ExportManager::ExportProviderFlag,QVariant> providedData); + QList<int> getColumnDataLengths(int columnCount, const QHash<ExportManager::ExportProviderFlag,QVariant> providedData); + bool beginDoc(const QString& title); + void endDoc(); + void setupConfig(); + void updateMargins(); + void drawDdl(const QString& objName, const QString& ddl); + void clearDataHeaders(); + void resetDataTable(); + void exportDataHeader(const QString& contents); + void exportDataColumnsHeader(const QStringList& columns); + void exportDataRow(const QList<QVariant>& data); + void exportObjectHeader(const QString& contents); + void exportObjectColumnsHeader(const QStringList& columns); + void exportTableColumnRow(SqliteCreateTable::Column* column); + void exportTableConstraintsRow(const QList<SqliteCreateTable::Constraint*>& constrList); + void exportObjectRow(const QStringList& values); + void exportObjectRow(const QString& values); + void checkForDataRender(); + void flushObjectPages(); + void drawObjectTopLine(int y); + void drawObjectCellHeaderBackground(int x1, int y1, int x2, int y2); + void drawFooter(); + void flushObjectRow(const ObjectRow& row, int y); + void flushObjectCell(const ObjectCell& cell, int x, int y, int w, int h); + void flushDataPages(bool forceRender = false); + void flushDataRowsPage(int columnStart, int columnEndBefore, int rowsToRender); + void flushDataRow(const DataRow& row, int& y, int columnStart, int columnEndBefore, int localRowNum); + void flushDataCell(const QRect& rect, const DataCell& cell); + void flushDataCell(const QRect& rect, const QString& contents, QTextOption* opt); + void flushDataHeaderRow(const DataRow& row, int& y, int totalColsWidth, int columnStart, int columnEndBefore); + void flushDataHeaderCell(int& x, int y, const DataRow& row, int col, QTextOption* opt); + void renderPageNumber(); + int getPageNumberHeight(); + void newPage(); + void calculateDataColumnWidths(const QStringList& columnNames, const QList<int>& columnDataLengths, int columnToExpand = -1); + void calculateDataRowHeights(); + int calculateRowHeight(int textWidth, const QString& contents); + int calculateRowHeight(int maxTextWidth, const QStringList& listContents); + int calculateBulletPrefixWidth(); + void calculateObjectColumnWidths(int columnToExpand = -1); + int correctMaxObjectColumnWidths(int colCount, int columnToExpand); + void calculateObjectRowHeights(); + int getDataColumnsWidth() const; + int getDataColumnsStartX() const; + int getContentsLeft() const; + int getContentsTop() const; + int getContentsRight() const; + int getContentsBottom() const; + qreal mmToPoints(qreal sizeMM); + + CFG_LOCAL(PdfExportConfig, cfg) + QPagedPaintDevice* pagedWriter = nullptr; + QPainter* painter = nullptr; + QTextOption* textOption = nullptr; + QFont stdFont; + QFont boldFont; + QFont italicFont; + int totalRows = 0; + QList<ObjectRow> bufferedObjectRows; // object rows (for exporting ddl of all object) + QList<DataRow> bufferedDataRows; // data rows + int totalHeaderRowsHeight = 0; // total height of all current header rows + int currentHeaderMinWidth = 0; // minimum width of the object header, required to extend data columns when hey are slimmer than this + QList<int> calculatedObjectColumnWidths; // object column widths calculated basing on header column widths and columns from other object rows + QList<int> calculatedDataColumnWidths; // data column widths calculated basing on header column widths and data column widths + QList<int> columnsPerPage; // number of columns that will fit on each page + QScopedPointer<DataRow> headerRow; // Top level header (object name) + QScopedPointer<DataRow> columnsHeaderRow; // columns header for data tables + int rowNumColumnWidth = 0; + int pageWidth = 0; + int pageHeight = 0; + int minRowHeight = 0; + int rowsToPrebuffer = 0; + int currentPage = -1; + int rowNum = 0; + int lastRowY = 0; + qreal pointsPerMm = 1.0; + int maxColWidth = 0; + int maxRowHeight = 0; + int cellDataLimit = 100; + + static QString bulletChar; + + // Configurable fields + int padding = 0; + bool printRowNum = true; + bool printPageNumbers = true; + int topMargin = 0; + int rightMargin = 0; + int leftMargin = 0; + int bottomMargin = 0; +}; + +#endif // PDFEXPORT_H diff --git a/Plugins/PdfExport/pdfexport.json b/Plugins/PdfExport/pdfexport.json new file mode 100644 index 0000000..5f1e2e4 --- /dev/null +++ b/Plugins/PdfExport/pdfexport.json @@ -0,0 +1,8 @@ +{ + "type": "ExportPlugin", + "title": "PDF export", + "description": "Provides PDF format for exporting.", + "version": 10000, + "author": "SalSoft", + "gui": true +} diff --git a/Plugins/PdfExport/pdfexport.qrc b/Plugins/PdfExport/pdfexport.qrc new file mode 100644 index 0000000..fce27e6 --- /dev/null +++ b/Plugins/PdfExport/pdfexport.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/forms"> + <file>pdfexport.ui</file> + </qresource> +</RCC> diff --git a/Plugins/PdfExport/pdfexport.ui b/Plugins/PdfExport/pdfexport.ui new file mode 100644 index 0000000..31dfce5 --- /dev/null +++ b/Plugins/PdfExport/pdfexport.ui @@ -0,0 +1,303 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PdfExportConfig</class> + <widget class="QWidget" name="PdfExportConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>332</width> + <height>492</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="sizeAndLayoutGroup"> + <property name="title"> + <string>Size and layout</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="6" column="1"> + <widget class="QSpinBox" name="byteLimitSpin"> + <property name="maximum"> + <number>999</number> + </property> + <property name="cfg" stdset="0"> + <string>PdfExport.MaxCellBytes</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="pageSizeLabel"> + <property name="text"> + <string>Page size:</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QSpinBox" name="bottomMarginSpin"> + <property name="maximum"> + <number>999999</number> + </property> + <property name="cfg" stdset="0"> + <string>PdfExport.BottomMargin</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSpinBox" name="rightMarginSpin"> + <property name="maximum"> + <number>999999</number> + </property> + <property name="cfg" stdset="0"> + <string>PdfExport.RightMargin</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="rightMarginLabel"> + <property name="text"> + <string>Right margin:</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="leftMarginLabel"> + <property name="text"> + <string>Left margin:</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="paddingLabel"> + <property name="text"> + <string>Cell padding:</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="byteLimitLabel"> + <property name="text"> + <string>Limit characters in single cell:</string> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QLabel" name="paddingMmLabel"> + <property name="text"> + <string>mm</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QSpinBox" name="paddingSpin"> + <property name="maximum"> + <number>999999</number> + </property> + <property name="cfg" stdset="0"> + <string>PdfExport.Padding</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="rightMarginMmLabel"> + <property name="text"> + <string>mm</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QLabel" name="topMarginMmLabel"> + <property name="text"> + <string>mm</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QSpinBox" name="topMarginSpin"> + <property name="maximum"> + <number>999999</number> + </property> + <property name="cfg" stdset="0"> + <string>PdfExport.TopMargin</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QLabel" name="bottomMarginMmLabel"> + <property name="text"> + <string>mm</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="bottomMarginLabel"> + <property name="text"> + <string>Bottom margin:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="ConfigComboBox" name="pageSizeCombo"> + <property name="modelName" stdset="0"> + <string>PdfExport.PageSizes</string> + </property> + <property name="cfg" stdset="0"> + <string>PdfExport.PageSize</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="leftMarginMmLabel"> + <property name="text"> + <string>mm</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="topMarginLabel"> + <property name="text"> + <string>Top margin:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="leftMarginSpin"> + <property name="maximum"> + <number>999999</number> + </property> + <property name="cfg" stdset="0"> + <string>PdfExport.LeftMargin</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="fontGroup"> + <property name="title"> + <string>Font</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="1"> + <widget class="QSpinBox" name="fontSizeSpin"> + <property name="cfg" stdset="0"> + <string>PdfExport.FontSize</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QFontComboBox" name="fontComboBox"> + <property name="cfg" stdset="0"> + <string>PdfExport.Font</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="colorsGroup"> + <property name="title"> + <string>Colors</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QLabel" name="headerBgColorLabel"> + <property name="text"> + <string>Headers background:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="ColorButton" name="headerBgColorButton"> + <property name="maximumSize"> + <size> + <width>50</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="cfg" stdset="0"> + <string>PdfExport.HeaderBgColor</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="nullColorLabel"> + <property name="text"> + <string>NULL value color:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="ColorButton" name="nullColorButton"> + <property name="maximumSize"> + <size> + <width>50</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="cfg" stdset="0"> + <string>PdfExport.NullValueColor</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="otherGroup"> + <property name="title"> + <string>Other settings</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QCheckBox" name="rowNumCheck"> + <property name="text"> + <string>Print row numbers for data</string> + </property> + <property name="cfg" stdset="0"> + <string>PdfExport.PrintRowNum</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="pageNumbersCheck"> + <property name="text"> + <string>Print page numbers</string> + </property> + <property name="cfg" stdset="0"> + <string>PdfExport.PrintPageNumbers</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ConfigComboBox</class> + <extends>QComboBox</extends> + <header>common/configcombobox.h</header> + </customwidget> + <customwidget> + <class>ColorButton</class> + <extends>QPushButton</extends> + <header>common/colorbutton.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/Plugins/PdfExport/pdfexport_global.h b/Plugins/PdfExport/pdfexport_global.h new file mode 100644 index 0000000..c30f2b5 --- /dev/null +++ b/Plugins/PdfExport/pdfexport_global.h @@ -0,0 +1,12 @@ +#ifndef PDFEXPORT_GLOBAL_H +#define PDFEXPORT_GLOBAL_H + +#include <QtCore/qglobal.h> + +#if defined(PDFEXPORT_LIBRARY) +# define PDFEXPORTSHARED_EXPORT Q_DECL_EXPORT +#else +# define PDFEXPORTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // PDFEXPORT_GLOBAL_H |
