From f0348a7cd858129328bc7927ec17c5e5db7e9950 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Fri, 14 Aug 2026 08:58:36 +0200 Subject: [PATCH] fix --- sources/pdf_links.cpp | 120 ++++++++++++++++++++------- sources/pdf_links.h | 29 ++++--- sources/print/projectprintwindow.cpp | 47 ++++++----- sources/print/projectprintwindow.h | 6 +- 4 files changed, 134 insertions(+), 68 deletions(-) diff --git a/sources/pdf_links.cpp b/sources/pdf_links.cpp index 3cb021220..5cf176f76 100644 --- a/sources/pdf_links.cpp +++ b/sources/pdf_links.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include #include @@ -399,8 +398,8 @@ void convertComponentInfoAnnotations(const QString &pdfPath, QByteArray data = f.readAll(); f.close(); - const QByteArray marker("http://componentinfo.local/"); - if (!data.contains(marker)) return; + const QByteArray markerPrefix("http://componentinfo.local/"); + if (!data.contains(markerPrefix)) return; int xrefStart = data.lastIndexOf("\nxref\n"); if (xrefStart == -1) xrefStart = data.lastIndexOf("\nxref "); @@ -433,20 +432,34 @@ void convertComponentInfoAnnotations(const QString &pdfPath, out.reserve(data.size()); int pos = 0; - int annotIdx = 0; + bool anyConverted = false; while (pos < body.size()) { - int markerPos = body.indexOf(marker, pos); + // Find next indexed marker: http://componentinfo.local/ + int markerPos = body.indexOf(markerPrefix, pos); if (markerPos == -1) { out.append(body.mid(pos)); break; } + // Extract index from marker URL + int idxStart = markerPos + markerPrefix.size(); + int idxEnd = idxStart; + while (idxEnd < body.size() && body[idxEnd] >= '0' && body[idxEnd] <= '9') + ++idxEnd; + if (idxEnd == idxStart) { + // No index — skip malformed marker + out.append(body.mid(pos, markerPos + markerPrefix.size() - pos)); + pos = markerPos + markerPrefix.size(); + continue; + } + int annotIndex = body.mid(idxStart, idxEnd - idxStart).toInt(); + int uriOpen = body.lastIndexOf("/URI (", markerPos); int closeParen = (uriOpen != -1) ? body.indexOf(')', markerPos) : -1; if (uriOpen == -1 || closeParen == -1 || uriOpen < pos) { - out.append(body.mid(pos, markerPos + marker.size() - pos)); - pos = markerPos + marker.size(); + out.append(body.mid(pos, idxEnd - pos)); + pos = idxEnd; continue; } @@ -455,43 +468,63 @@ void convertComponentInfoAnnotations(const QString &pdfPath, // Find the /A << that opens the action dict containing /S /URI. // Qt always writes: /A <<\n/S /URI\n/URI (...)\n>>\n>> - // We need /Contents at the annotation level, NOT inside /A <<. - int aDictOpen = body.lastIndexOf("/A <<", (sUriPos != -1) ? sUriPos : uriOpen); - int copyEnd = (aDictOpen != -1 && aDictOpen >= pos) ? aDictOpen - : (sUriPos != -1 && sUriPos >= pos) ? sUriPos - : uriOpen; - out.append(body.mid(pos, copyEnd - pos)); + int aDictOpen = (sUriPos != -1) + ? body.lastIndexOf("/A <<", sUriPos) + : -1; - // Get text from annotations list (matched by order) - if (annotIdx >= annotations.size()) { - // Keep original action dict intact - out.append(body.mid(copyEnd, closeParen + 1 - copyEnd)); + // If /A << is not found, leave this annotation untouched + if (aDictOpen == -1 || aDictOpen < pos) { + out.append(body.mid(pos, closeParen + 1 - pos)); pos = closeParen + 1; continue; } - QByteArray contents = annotations[annotIdx].contents.toUtf8(); - ++annotIdx; + // Validate index + if (annotIndex < 0 || annotIndex >= annotations.size()) { + // Index out of range — keep original annotation intact + out.append(body.mid(pos, closeParen + 1 - pos)); + pos = closeParen + 1; + continue; + } - // Replace /Subtype /Link with /Subtype /Text - int subTypePos = out.lastIndexOf("/Subtype /Link"); + // Copy annotation dict header up to /A << + out.append(body.mid(pos, aDictOpen - pos)); + + QByteArray contents = annotations[annotIndex].contents.toUtf8(); + + // Replace /Subtype /Link with /Subtype /Text, bounded to current object. + // Search only the bytes we just appended (the current annotation header). + int searchStart = out.size() - (aDictOpen - pos); + int subTypePos = out.indexOf("/Subtype /Link", searchStart); if (subTypePos != -1) { out.replace(subTypePos, 14, "/Subtype /Text"); } // Encode as UTF-16BE hex with BOM for proper Unicode support (Umlauten etc.). // PDF spec: hex strings starting with FE FF are interpreted as UTF-16BE. - QTextCodec *codec = QTextCodec::codecForName("UTF-16BE"); QByteArray utf16be; utf16be.append('\xfe'); utf16be.append('\xff'); - if (codec) { - utf16be += codec->fromUnicode(QString::fromUtf8(contents)); - } else { - // Fallback: manual UTF-16BE encoding - QString str = QString::fromUtf8(contents); - for (int i = 0; i < str.size(); ++i) { - ushort cp = str.at(i).unicode(); + { + for (int i = 0; i < contents.size(); ) { + ushort cp = 0; + uchar c = static_cast(contents.at(i)); + if (c < 0x80) { + cp = c; + ++i; + } else if ((c & 0xE0) == 0xC0 && i + 1 < contents.size()) { + cp = ((c & 0x1F) << 6) + | (static_cast(contents.at(i + 1)) & 0x3F); + i += 2; + } else if ((c & 0xF0) == 0xE0 && i + 2 < contents.size()) { + cp = ((c & 0x0F) << 12) + | ((static_cast(contents.at(i + 1)) & 0x3F) << 6) + | (static_cast(contents.at(i + 2)) & 0x3F); + i += 3; + } else { + cp = 0xFFFD; // replacement character + ++i; + } utf16be.append(static_cast((cp >> 8) & 0xFF)); utf16be.append(static_cast(cp & 0xFF)); } @@ -509,9 +542,10 @@ void convertComponentInfoAnnotations(const QString &pdfPath, } else { pos = closeParen + 1; } + anyConverted = true; } - if (annotIdx == 0) return; + if (!anyConverted) return; // Append empty Form XObject (shared by all annotations for invisible appearance) QByteArray emptyXObj; @@ -556,6 +590,7 @@ void convertComponentInfoAnnotations(const QString &pdfPath, } } + // Copy trailer and bump /Size to account for the new XObject QByteArray trailer; { int tPos = data.indexOf("trailer", xrefStart); @@ -569,6 +604,31 @@ void convertComponentInfoAnnotations(const QString &pdfPath, if (trailer.isEmpty()) trailer = "trailer\n<<>>\n%%EOF"; + // Bump /Size: original was maxObjNum+1, now it's emptyXObjNum+1 + { + int sizePos = trailer.indexOf("/Size "); + if (sizePos != -1) { + int numStart = sizePos + 6; + int numEnd = numStart; + while (numEnd < trailer.size() && trailer[numEnd] >= '0' && trailer[numEnd] <= '9') + ++numEnd; + if (numEnd > numStart) { + trailer.replace(numStart, numEnd - numStart, + QByteArray::number(emptyXObjNum + 1)); + } + } + } + + // Remove duplicate startxref if present in copied trailer + { + int stPos = trailer.indexOf("\nstartxref\n"); + if (stPos != -1) + trailer = trailer.left(stPos); + // Ensure trailer ends with %%EOF + if (!trailer.endsWith("%%EOF\n")) + trailer += "\n%%EOF\n"; + } + QByteArray result; result.reserve(out.size() + xref.size() + trailer.size() + 64); result += out; diff --git a/sources/pdf_links.h b/sources/pdf_links.h index 07993f087..27e266420 100644 --- a/sources/pdf_links.h +++ b/sources/pdf_links.h @@ -66,20 +66,25 @@ namespace PdfLinks { const QMap &pageMap, const QString &outputFileName); -/** - Post-process a Qt-generated PDF file: rewrite every "/S /URI" link - annotation into a native internal "/S /GoTo" action (page + /FitR or - /Fit destination) and rebuild the xref table. No-op if the file has no - such annotations. -*/ -void convertUriToGoTo(const QString &pdfPath); + /** + Post-process a Qt-generated PDF file: rewrite every "/S /URI" link + annotation into a native internal "/S /GoTo" action (page + /FitR or + /Fit destination) and rebuild the xref table. No-op if the file has no + such annotations. + */ + void convertUriToGoTo(const QString &pdfPath); -struct ComponentInfo { - QString contents; -}; + struct ComponentInfo { + QString contents; + }; -void convertComponentInfoAnnotations(const QString &pdfPath, - const QList &annotations); + /** + Post-process a Qt-generated PDF file: convert component-info placeholder + link annotations (http://componentinfo.local/) into invisible text + annotations with the actual component info as /Contents. + */ + void convertComponentInfoAnnotations(const QString &pdfPath, + const QList &annotations); } diff --git a/sources/print/projectprintwindow.cpp b/sources/print/projectprintwindow.cpp index 0edec9cce..0215dd44e 100644 --- a/sources/print/projectprintwindow.cpp +++ b/sources/print/projectprintwindow.cpp @@ -432,8 +432,9 @@ void ProjectPrintWindow::printDiagram(Diagram *diagram, bool fit_page, QPainter ////Collect component info for popup annotations//// if (ui->m_component_info_cb->isChecked()) { + int annotIndex = 0; for (auto *item : diagram->items()) { - auto *el = dynamic_cast(item); + auto *el = qgraphicsitem_cast(item); if (!el) continue; // Skip reports and slaves @@ -444,32 +445,32 @@ void ProjectPrintWindow::printDiagram(Diagram *diagram, bool fit_page, QPainter auto info = el->elementInformations(); if (info.count() == 0) continue; - // Build info text - QStringList lines; - for (const QString &key : {"label", "manufacturer", "designation", "description"}) { - if (info.contains(key) && !info.value(key).toString().isEmpty()) - lines << QETInformation::translatedInfoKey(key) + ": " + info.value(key).toString(); - } - for (const QString &key : info.keys()) { - if (key == "formula") continue; - QString translated = QETInformation::translatedInfoKey(key); - if (lines.contains(translated + ": " + info.value(key).toString())) continue; - if (info.value(key).toString().isEmpty()) continue; - lines << translated + ": " + info.value(key).toString(); - } + // Build info text + QStringList lines; + for (const QString &key : {"label", "manufacturer", "designation", "description"}) { + if (info.contains(key) && !info.value(key).toString().isEmpty()) + lines << QETInformation::translatedInfoKey(key) + ": " + info.value(key).toString(); + } + for (const QString &key : info.keys()) { + if (key == "formula") continue; + QString translated = QETInformation::translatedInfoKey(key); + if (lines.contains(translated + ": " + info.value(key).toString())) continue; + if (info.value(key).toString().isEmpty()) continue; + lines << translated + ": " + info.value(key).toString(); + } if (lines.isEmpty()) continue; - // Compute element rect in device pixels - QRectF elemScene = el->mapRectToScene(el->boundingRect()); - QRectF devRect = fit.mapRect(elemScene); + // Compute element rect in device pixels + QRectF elemScene = el->mapRectToScene(el->boundingRect()); + QRectF devRect = fit.mapRect(elemScene); - PdfLinks::ComponentInfo ci; - ci.contents = lines.join("\n"); - m_componentInfoList.append(ci); + PdfLinks::ComponentInfo ci; + ci.contents = lines.join("\n"); + m_componentInfoList.append(ci); - // Create a link annotation as placeholder — post-processing - // will convert it to an invisible text annotation with the actual content - pdfEngine->drawHyperlink(devRect, QUrl("http://componentinfo.local/")); + // Create a link annotation as placeholder — post-processing + // will convert it to an invisible text annotation with the actual content + pdfEngine->drawHyperlink(devRect, QUrl(QString("http://componentinfo.local/%1").arg(annotIndex++))); } } ////Component info end//// diff --git a/sources/print/projectprintwindow.h b/sources/print/projectprintwindow.h index 72fa8e41d..74d9ae7e6 100644 --- a/sources/print/projectprintwindow.h +++ b/sources/print/projectprintwindow.h @@ -56,9 +56,9 @@ class ProjectPrintWindow : public QMainWindow void on_m_draw_titleblock_cb_clicked(); void on_m_keep_conductor_color_cb_clicked(); void on_m_draw_terminal_cb_clicked(); - void on_m_draw_terminal_names_cb_clicked(); - void on_m_component_info_cb_clicked(); - void on_m_fit_in_page_cb_clicked(); + void on_m_draw_terminal_names_cb_clicked(); + void on_m_component_info_cb_clicked(); + void on_m_fit_in_page_cb_clicked(); void on_m_use_full_page_cb_clicked(); void on_m_zoom_out_action_triggered(); void on_m_zoom_in_action_triggered();