diff --git a/lib/Epub/Epub/Section.cpp b/lib/Epub/Epub/Section.cpp index 55357d1..4cc8285 100644 --- a/lib/Epub/Epub/Section.cpp +++ b/lib/Epub/Epub/Section.cpp @@ -214,27 +214,28 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression, [this](std::unique_ptr page) { this->onPageComplete(std::move(page)); }, cachePath); - // Track which inline footnotes are actually referenced in this file + // Track which inline footnotes AND paragraph notes are actually referenced in this file std::set rewrittenInlineIds; int noterefCount = 0; visitor.setNoterefCallback([this, ¬erefCount, &rewrittenInlineIds](Noteref& noteref) { Serial.printf("[%lu] [SCT] Callback noteref: %s -> %s\n", millis(), noteref.number, noteref.href); - // Check if this was rewritten to an inline footnote + // Extract the ID from the href for tracking std::string href(noteref.href); - if (href.find("inline_") == 0) { - // Extract ID from "inline_N3.html#N3" + + // Check if this was rewritten to an inline or paragraph note + if (href.find("inline_") == 0 || href.find("pnote_") == 0) { size_t underscorePos = href.find('_'); size_t dotPos = href.find('.'); if (underscorePos != std::string::npos && dotPos != std::string::npos) { - std::string inlineId = href.substr(underscorePos + 1, dotPos - underscorePos - 1); - rewrittenInlineIds.insert(inlineId); - Serial.printf("[%lu] [SCT] Marked inline footnote as rewritten: %s\n", - millis(), inlineId.c_str()); + std::string noteId = href.substr(underscorePos + 1, dotPos - underscorePos - 1); + rewrittenInlineIds.insert(noteId); + Serial.printf("[%lu] [SCT] Marked note as rewritten: %s\n", + millis(), noteId.c_str()); } - } else { + }else { // Normal external footnote epub->markAsFootnotePage(noteref.href); } @@ -312,7 +313,6 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression, Serial.printf("[%lu] [SCT] Generated inline footnote file\n", millis()); - // Add as virtual spine item (full path for epub to find it) int virtualIndex = epub->addVirtualSpineItem(fullPath); Serial.printf("[%lu] [SCT] Added virtual spine item at index %d\n", millis(), virtualIndex); @@ -325,6 +325,65 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression, } } +// Generate paragraph note HTML files +Serial.printf("[%lu] [SCT] Found %d paragraph notes\n", millis(), visitor.paragraphNoteCount); + +for (int i = 0; i < visitor.paragraphNoteCount; i++) { + const char* pnoteId = visitor.paragraphNotes[i].id; + const char* pnoteText = visitor.paragraphNotes[i].text; + + if (!pnoteText || strlen(pnoteText) == 0) { + continue; + } + + // Check if this paragraph note was referenced + if (rewrittenInlineIds.find(std::string(pnoteId)) == rewrittenInlineIds.end()) { + Serial.printf("[%lu] [SCT] Skipping unreferenced paragraph note: %s\n", millis(), pnoteId); + continue; + } + + // Create filename: pnote_rnote1.html + char pnoteFilename[64]; + snprintf(pnoteFilename, sizeof(pnoteFilename), "pnote_%s.html", pnoteId); + + std::string fullPath = epub->getCachePath() + "/" + std::string(pnoteFilename); + + Serial.printf("[%lu] [SCT] Generating paragraph note file: %s\n", millis(), fullPath.c_str()); + + File file = SD.open(fullPath.c_str(), FILE_WRITE, true); + if (file) { + file.println(""); + file.println(""); + file.println(""); + file.println(""); + file.println(""); + file.println("Note"); + file.println(""); + file.println(""); + file.print("

"); + + if (!writeEscapedXml(file, pnoteText)) { + Serial.printf("[%lu] [SCT] Warning: writeEscapedXml may have failed\n", millis()); + } + + file.println("

"); + file.println(""); + file.println(""); + file.close(); + + Serial.printf("[%lu] [SCT] Generated paragraph note file\n", millis()); + + int virtualIndex = epub->addVirtualSpineItem(fullPath); + Serial.printf("[%lu] [SCT] Added virtual spine item at index %d\n", millis(), virtualIndex); + + char newHref[128]; + snprintf(newHref, sizeof(newHref), "%s#%s", pnoteFilename, pnoteId); + epub->markAsFootnotePage(newHref); + } +} + Serial.printf("[%lu] [SCT] Total noterefs found: %d\n", millis(), noterefCount); writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft, extraParagraphSpacing); @@ -343,4 +402,4 @@ std::unique_ptr Section::loadPageFromSD() const { auto page = Page::deserialize(inputFile); inputFile.close(); return page; -} \ No newline at end of file +} diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index 4580a3b..3431e67 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -84,12 +84,29 @@ void ChapterHtmlSlimParser::addFootnoteToCurrentPage(const char* number, const c strncpy(currentPageFootnotes[currentPageFootnoteCount].href, rewrittenHref, 63); currentPageFootnotes[currentPageFootnoteCount].href[63] = '\0'; - Serial.printf("[%lu] [ADDFT] ✓ Rewrote inline href to: %s\n", millis(), rewrittenHref); + Serial.printf("[%lu] [ADDFT] Rewrote inline href to: %s\n", millis(), rewrittenHref); foundInline = true; break; } } + //Check if we have this as a paragraph note + if (!foundInline) { + for (int i = 0; i < paragraphNoteCount; i++) { + if (strcmp(paragraphNotes[i].id, inlineId) == 0) { + char rewrittenHref[64]; + snprintf(rewrittenHref, sizeof(rewrittenHref), "pnote_%s.html#%s", inlineId, inlineId); + + strncpy(currentPageFootnotes[currentPageFootnoteCount].href, rewrittenHref, 63); + currentPageFootnotes[currentPageFootnoteCount].href[63] = '\0'; + + Serial.printf("[%lu] [ADDFT] Rewrote paragraph note href to: %s\n", millis(), rewrittenHref); + foundInline = true; + break; + } + } + } + if (!foundInline) { // Normal href, just copy it strncpy(currentPageFootnotes[currentPageFootnoteCount].href, href, 63); @@ -112,6 +129,43 @@ void ChapterHtmlSlimParser::addFootnoteToCurrentPage(const char* number, const c void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) { auto* self = static_cast(userData); + // ============================================================================ + // PASS 1: Detect and collect

+ // ============================================================================ + if (strcmp(name, "p") == 0 && self->isPass1CollectingAsides) { + const char* classAttr = getAttribute(atts, "class"); + + if (classAttr && (strcmp(classAttr, "note") == 0 || strstr(classAttr, "note"))) { + Serial.printf("[%lu] [PNOTE] Found paragraph note (pass1=1)\n", millis()); + + self->insideParagraphNote = true; + self->paragraphNoteDepth = self->depth; + self->currentParagraphNoteTextLen = 0; + self->currentParagraphNoteText[0] = '\0'; + self->currentParagraphNoteId[0] = '\0'; + + self->depth += 1; + return; + } + } + + // Inside paragraph note in Pass 1, look for + if (self->insideParagraphNote && self->isPass1CollectingAsides && strcmp(name, "a") == 0) { + const char* id = getAttribute(atts, "id"); + + if (id && strncmp(id, "rnote", 5) == 0) { + strncpy(self->currentParagraphNoteId, id, 15); + self->currentParagraphNoteId[15] = '\0'; + Serial.printf("[%lu] [PNOTE] Found note ID: %s\n", millis(), id); + } + + self->depth += 1; + return; + } + + // ============================================================================ + // PASS 1: Detect and collect