Merge branch 'master' of https://github.com/jpirnay/crosspoint-reader into feat-koysnc-xpath

This commit is contained in:
jpirnay
2026-02-27 09:23:44 +01:00
90 changed files with 2911 additions and 1242 deletions
+27
View File
@@ -858,3 +858,30 @@ float Epub::calculateProgress(const int currentSpineIndex, const float currentSp
const float totalProgress = static_cast<float>(prevChapterSize) + sectionProgSize;
return totalProgress / static_cast<float>(bookSize);
}
int Epub::resolveHrefToSpineIndex(const std::string& href) const {
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return -1;
// Extract filename (remove #anchor)
std::string target = href;
size_t hashPos = target.find('#');
if (hashPos != std::string::npos) target = target.substr(0, hashPos);
// Same-file reference (anchor-only)
if (target.empty()) return -1;
// Extract just the filename for comparison
size_t targetSlash = target.find_last_of('/');
std::string targetFilename = (targetSlash != std::string::npos) ? target.substr(targetSlash + 1) : target;
for (int i = 0; i < getSpineItemsCount(); i++) {
const auto& spineHref = getSpineItem(i).href;
// Try exact match first
if (spineHref == target) return i;
// Then filename-only match
size_t spineSlash = spineHref.find_last_of('/');
std::string spineFilename = (spineSlash != std::string::npos) ? spineHref.substr(spineSlash + 1) : spineHref;
if (spineFilename == targetFilename) return i;
}
return -1;
}
+1
View File
@@ -72,4 +72,5 @@ class Epub {
size_t getBookSize() const;
float calculateProgress(int currentSpineIndex, float currentSpineRead) const;
CssParser* getCssParser() const { return cssParser.get(); }
int resolveHrefToSpineIndex(const std::string& href) const;
};
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <cstring>
struct FootnoteEntry {
char number[24];
char href[64];
FootnoteEntry() {
number[0] = '\0';
href[0] = '\0';
}
};
+31
View File
@@ -67,6 +67,18 @@ bool Page::serialize(FsFile& file) const {
}
}
// Serialize footnotes (clamp to MAX_FOOTNOTES_PER_PAGE to match addFootnote/deserialize limits)
const uint16_t fnCount = std::min<uint16_t>(footnotes.size(), MAX_FOOTNOTES_PER_PAGE);
serialization::writePod(file, fnCount);
for (uint16_t i = 0; i < fnCount; i++) {
const auto& fn = footnotes[i];
if (file.write(fn.number, sizeof(fn.number)) != sizeof(fn.number) ||
file.write(fn.href, sizeof(fn.href)) != sizeof(fn.href)) {
LOG_ERR("PGE", "Failed to write footnote");
return false;
}
}
return true;
}
@@ -92,5 +104,24 @@ std::unique_ptr<Page> Page::deserialize(FsFile& file) {
}
}
// Deserialize footnotes
uint16_t fnCount;
serialization::readPod(file, fnCount);
if (fnCount > MAX_FOOTNOTES_PER_PAGE) {
LOG_ERR("PGE", "Invalid footnote count %u", fnCount);
return nullptr;
}
page->footnotes.resize(fnCount);
for (uint16_t i = 0; i < fnCount; i++) {
auto& entry = page->footnotes[i];
if (file.read(entry.number, sizeof(entry.number)) != sizeof(entry.number) ||
file.read(entry.href, sizeof(entry.href)) != sizeof(entry.href)) {
LOG_ERR("PGE", "Failed to read footnote %u", i);
return nullptr;
}
entry.number[sizeof(entry.number) - 1] = '\0';
entry.href[sizeof(entry.href) - 1] = '\0';
}
return page;
}
+14
View File
@@ -5,6 +5,7 @@
#include <utility>
#include <vector>
#include "FootnoteEntry.h"
#include "blocks/ImageBlock.h"
#include "blocks/TextBlock.h"
@@ -57,6 +58,19 @@ class Page {
public:
// the list of block index and line numbers on this page
std::vector<std::shared_ptr<PageElement>> elements;
std::vector<FootnoteEntry> footnotes;
static constexpr uint16_t MAX_FOOTNOTES_PER_PAGE = 16;
void addFootnote(const char* number, const char* href) {
if (footnotes.size() >= MAX_FOOTNOTES_PER_PAGE) return; // Cap per-page footnotes
FootnoteEntry entry;
strncpy(entry.number, number, sizeof(entry.number) - 1);
entry.number[sizeof(entry.number) - 1] = '\0';
strncpy(entry.href, href, sizeof(entry.href) - 1);
entry.href[sizeof(entry.href) - 1] = '\0';
footnotes.push_back(entry);
}
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
bool serialize(FsFile& file) const;
static std::unique_ptr<Page> deserialize(FsFile& file);
+1
View File
@@ -29,6 +29,7 @@ class TextBlock final : public Block {
const BlockStyle& getBlockStyle() const { return blockStyle; }
const std::vector<std::string>& getWords() const { return words; }
bool isEmpty() override { return words.empty(); }
size_t wordCount() const { return words.size(); }
// given a renderer works out where to break the words into lines
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
BlockType getType() override { return TEXT_BLOCK; }
+95 -57
View File
@@ -1,4 +1,4 @@
// from
// based on
// https://github.com/atomic14/diy-esp32-epub-reader/blob/2c2f57fdd7e2a788d14a0bcb26b9e845a47aac42/lib/Epub/RubbishHtmlParser/htmlEntities.cpp
#include "htmlEntities.h"
@@ -10,67 +10,105 @@ struct EntityPair {
const char* value;
};
static const EntityPair ENTITY_LOOKUP[] = {
{"&quot;", "\""}, {"&frasl;", ""}, {"&amp;", "&"}, {"&lt;", "<"}, {"&gt;", ">"},
{"&Agrave;", "À"}, {"&Aacute;", "Á"}, {"&Acirc;", "Â"}, {"&Atilde;", "Ã"}, {"&Auml;", "Ä"},
{"&Aring;", "Å"}, {"&AElig;", "Æ"}, {"&Ccedil;", "Ç"}, {"&Egrave;", "È"}, {"&Eacute;", "É"},
{"&Ecirc;", "Ê"}, {"&Euml;", "Ë"}, {"&Igrave;", "Ì"}, {"&Iacute;", "Í"}, {"&Icirc;", "Î"},
{"&Iuml;", "Ï"}, {"&ETH;", "Ð"}, {"&Ntilde;", "Ñ"}, {"&Ograve;", "Ò"}, {"&Oacute;", "Ó"},
{"&Ocirc;", "Ô"}, {"&Otilde;", "Õ"}, {"&Ouml;", "Ö"}, {"&Oslash;", "Ø"}, {"&Ugrave;", "Ù"},
{"&Uacute;", "Ú"}, {"&Ucirc;", "Û"}, {"&Uuml;", "Ü"}, {"&Yacute;", "Ý"}, {"&THORN;", "Þ"},
{"&szlig;", "ß"}, {"&agrave;", "à"}, {"&aacute;", "á"}, {"&acirc;", "â"}, {"&atilde;", "ã"},
{"&auml;", "ä"}, {"&aring;", "å"}, {"&aelig;", "æ"}, {"&ccedil;", "ç"}, {"&egrave;", "è"},
{"&eacute;", "é"}, {"&ecirc;", "ê"}, {"&euml;", "ë"}, {"&igrave;", "ì"}, {"&iacute;", "í"},
{"&icirc;", "î"}, {"&iuml;", "ï"}, {"&eth;", "ð"}, {"&ntilde;", "ñ"}, {"&ograve;", "ò"},
{"&oacute;", "ó"}, {"&ocirc;", "ô"}, {"&otilde;", "õ"}, {"&ouml;", "ö"}, {"&oslash;", "ø"},
{"&ugrave;", "ù"}, {"&uacute;", "ú"}, {"&ucirc;", "û"}, {"&uuml;", "ü"}, {"&yacute;", "ý"},
{"&thorn;", "þ"}, {"&yuml;", "ÿ"}, {"&nbsp;", "\xC2\xA0"}, {"&iexcl;", "¡"}, {"&cent;", "¢"},
{"&pound;", "£"}, {"&curren;", "¤"}, {"&yen;", "¥"}, {"&brvbar;", "¦"}, {"&sect;", "§"},
{"&uml;", "¨"}, {"&copy;", "©"}, {"&ordf;", "ª"}, {"&laquo;", "«"}, {"&not;", "¬"},
{"&shy;", "­"}, {"&reg;", "®"}, {"&macr;", "¯"}, {"&deg;", "°"}, {"&plusmn;", "±"},
{"&sup2;", "²"}, {"&sup3;", "³"}, {"&acute;", "´"}, {"&micro;", "µ"}, {"&para;", ""},
{"&cedil;", "¸"}, {"&sup1;", "¹"}, {"&ordm;", "º"}, {"&raquo;", "»"}, {"&frac14;", "¼"},
{"&frac12;", "½"}, {"&frac34;", "¾"}, {"&iquest;", "¿"}, {"&times;", "×"}, {"&divide;", "÷"},
{"&forall;", ""}, {"&part;", ""}, {"&exist;", ""}, {"&empty;", ""}, {"&nabla;", ""},
{"&isin;", ""}, {"&notin;", ""}, {"&ni;", ""}, {"&prod;", ""}, {"&sum;", ""},
{"&minus;", ""}, {"&lowast;", ""}, {"&radic;", ""}, {"&prop;", ""}, {"&infin;", ""},
{"&ang;", ""}, {"&and;", ""}, {"&or;", ""}, {"&cap;", ""}, {"&cup;", ""},
{"&int;", ""}, {"&there4;", ""}, {"&sim;", ""}, {"&cong;", ""}, {"&asymp;", ""},
{"&ne;", ""}, {"&equiv;", ""}, {"&le;", ""}, {"&ge;", ""}, {"&sub;", ""},
{"&sup;", ""}, {"&nsub;", ""}, {"&sube;", ""}, {"&supe;", ""}, {"&oplus;", ""},
{"&otimes;", ""}, {"&perp;", ""}, {"&sdot;", ""}, {"&Alpha;", "Α"}, {"&Beta;", "Β"},
{"&Gamma;", "Γ"}, {"&Delta;", "Δ"}, {"&Epsilon;", "Ε"}, {"&Zeta;", "Ζ"}, {"&Eta;", "Η"},
{"&Theta;", "Θ"}, {"&Iota;", "Ι"}, {"&Kappa;", "Κ"}, {"&Lambda;", "Λ"}, {"&Mu;", "Μ"},
{"&Nu;", "Ν"}, {"&Xi;", "Ξ"}, {"&Omicron;", "Ο"}, {"&Pi;", "Π"}, {"&Rho;", "Ρ"},
{"&Sigma;", "Σ"}, {"&Tau;", "Τ"}, {"&Upsilon;", "Υ"}, {"&Phi;", "Φ"}, {"&Chi;", "Χ"},
{"&Psi;", "Ψ"}, {"&Omega;", "Ω"}, {"&alpha;", "α"}, {"&beta;", "β"}, {"&gamma;", "γ"},
{"&delta;", "δ"}, {"&epsilon;", "ε"}, {"&zeta;", "ζ"}, {"&eta;", "η"}, {"&theta;", "θ"},
{"&iota;", "ι"}, {"&kappa;", "κ"}, {"&lambda;", "λ"}, {"&mu;", "μ"}, {"&nu;", "ν"},
{"&xi;", "ξ"}, {"&omicron;", "ο"}, {"&pi;", "π"}, {"&rho;", "ρ"}, {"&sigmaf;", "ς"},
{"&sigma;", "σ"}, {"&tau;", "τ"}, {"&upsilon;", "υ"}, {"&phi;", "φ"}, {"&chi;", "χ"},
{"&psi;", "ψ"}, {"&omega;", "ω"}, {"&thetasym;", "ϑ"}, {"&upsih;", "ϒ"}, {"&piv;", "ϖ"},
{"&OElig;", "Œ"}, {"&oelig;", "œ"}, {"&Scaron;", "Š"}, {"&scaron;", "š"}, {"&Yuml;", "Ÿ"},
{"&fnof;", "ƒ"}, {"&circ;", "ˆ"}, {"&tilde;", "˜"}, {"&ensp;", ""}, {"&emsp;", ""},
{"&thinsp;", ""}, {"&zwnj;", ""}, {"&zwj;", ""}, {"&lrm;", ""}, {"&rlm;", ""},
{"&ndash;", ""}, {"&mdash;", ""}, {"&lsquo;", ""}, {"&rsquo;", ""}, {"&sbquo;", ""},
{"&ldquo;", ""}, {"&rdquo;", ""}, {"&bdquo;", ""}, {"&dagger;", ""}, {"&Dagger;", ""},
{"&bull;", ""}, {"&hellip;", ""}, {"&permil;", ""}, {"&prime;", ""}, {"&Prime;", ""},
{"&lsaquo;", ""}, {"&rsaquo;", ""}, {"&oline;", ""}, {"&euro;", ""}, {"&trade;", ""},
{"&larr;", ""}, {"&uarr;", ""}, {"&rarr;", ""}, {"&darr;", ""}, {"&harr;", ""},
{"&crarr;", ""}, {"&lceil;", ""}, {"&rceil;", ""}, {"&lfloor;", ""}, {"&rfloor;", ""},
{"&loz;", ""}, {"&spades;", ""}, {"&clubs;", ""}, {"&hearts;", ""}, {"&diams;", ""}};
// Sorted lexicographically by key to allow binary search.
static constexpr EntityPair ENTITY_LOOKUP[] = {
{"&AElig;", "Æ"}, {"&Aacute;", "Á"}, {"&Acirc;", "Â"}, {"&Agrave;", "À"}, {"&Alpha;", "Α"},
{"&Aring;", "Å"}, {"&Atilde;", "Ã"}, {"&Auml;", "Ä"}, {"&Beta;", "Β"}, {"&Ccedil;", "Ç"},
{"&Chi;", "Χ"}, {"&Dagger;", ""}, {"&Delta;", "Δ"}, {"&ETH;", "Ð"}, {"&Eacute;", "É"},
{"&Ecirc;", "Ê"}, {"&Egrave;", "È"}, {"&Epsilon;", "Ε"}, {"&Eta;", "Η"}, {"&Euml;", "Ë"},
{"&Gamma;", "Γ"}, {"&Iacute;", "Í"}, {"&Icirc;", "Î"}, {"&Igrave;", "Ì"}, {"&Iota;", "Ι"},
{"&Iuml;", "Ï"}, {"&Kappa;", "Κ"}, {"&Lambda;", "Λ"}, {"&Mu;", "Μ"}, {"&Ntilde;", "Ñ"},
{"&Nu;", "Ν"}, {"&OElig;", "Œ"}, {"&Oacute;", "Ó"}, {"&Ocirc;", "Ô"}, {"&Ograve;", "Ò"},
{"&Omega;", "Ω"}, {"&Omicron;", "Ο"}, {"&Oslash;", "Ø"}, {"&Otilde;", "Õ"}, {"&Ouml;", "Ö"},
{"&Phi;", "Φ"}, {"&Pi;", "Π"}, {"&Prime;", ""}, {"&Psi;", "Ψ"}, {"&Rho;", "Ρ"},
{"&Scaron;", "Š"}, {"&Sigma;", "Σ"}, {"&THORN;", "Þ"}, {"&Tau;", "Τ"}, {"&Theta;", "Θ"},
{"&Uacute;", "Ú"}, {"&Ucirc;", "Û"}, {"&Ugrave;", "Ù"}, {"&Upsilon;", "Υ"}, {"&Uuml;", "Ü"},
{"&Xi;", "Ξ"}, {"&Yacute;", "Ý"}, {"&Yuml;", "Ÿ"}, {"&Zeta;", "Ζ"}, {"&aacute;", "á"},
{"&acirc;", "â"}, {"&acute;", "´"}, {"&aelig;", "æ"}, {"&agrave;", "à"}, {"&alpha;", "α"},
{"&amp;", "&"}, {"&and;", ""}, {"&ang;", ""}, {"&aring;", "å"}, {"&asymp;", ""},
{"&atilde;", "ã"}, {"&auml;", "ä"}, {"&bdquo;", ""}, {"&beta;", "β"}, {"&brvbar;", "¦"},
{"&bull;", ""}, {"&cap;", ""}, {"&ccedil;", "ç"}, {"&cedil;", "¸"}, {"&cent;", "¢"},
{"&chi;", "χ"}, {"&circ;", "ˆ"}, {"&clubs;", ""}, {"&cong;", ""}, {"&copy;", "©"},
{"&crarr;", ""}, {"&cup;", ""}, {"&curren;", "¤"}, {"&dagger;", ""}, {"&darr;", ""},
{"&deg;", "°"}, {"&delta;", "δ"}, {"&diams;", ""}, {"&divide;", "÷"}, {"&eacute;", "é"},
{"&ecirc;", "ê"}, {"&egrave;", "è"}, {"&empty;", ""}, {"&emsp;", " "}, {"&ensp;", " "},
{"&epsilon;", "ε"}, {"&equiv;", ""}, {"&eta;", "η"}, {"&eth;", "ð"}, {"&euml;", "ë"},
{"&euro;", ""}, {"&exist;", ""}, {"&fnof;", "ƒ"}, {"&forall;", ""}, {"&frac12;", "½"},
{"&frac14;", "¼"}, {"&frac34;", "¾"}, {"&frasl;", ""}, {"&gamma;", "γ"}, {"&ge;", ""},
{"&gt;", ">"}, {"&harr;", ""}, {"&hearts;", ""}, {"&hellip;", ""}, {"&iacute;", "í"},
{"&icirc;", "î"}, {"&iexcl;", "¡"}, {"&igrave;", "ì"}, {"&infin;", ""}, {"&int;", ""},
{"&iota;", "ι"}, {"&iquest;", "¿"}, {"&isin;", ""}, {"&iuml;", "ï"}, {"&kappa;", "κ"},
{"&lambda;", "λ"}, {"&laquo;", "«"}, {"&larr;", ""}, {"&lceil;", ""}, {"&ldquo;", "\u201C"},
{"&le;", ""}, {"&lfloor;", ""}, {"&lowast;", ""}, {"&loz;", ""}, {"&lrm;", "\u200E"},
{"&lsaquo;", ""}, {"&lsquo;", "\u2018"}, {"&lt;", "<"}, {"&macr;", "¯"}, {"&mdash;", ""},
{"&micro;", "µ"}, {"&minus;", ""}, {"&mu;", "μ"}, {"&nabla;", ""}, {"&nbsp;", "\xC2\xA0"},
{"&ndash;", ""}, {"&ne;", ""}, {"&ni;", ""}, {"&not;", "¬"}, {"&notin;", ""},
{"&nsub;", ""}, {"&ntilde;", "ñ"}, {"&nu;", "ν"}, {"&oacute;", "ó"}, {"&ocirc;", "ô"},
{"&oelig;", "œ"}, {"&ograve;", "ò"}, {"&oline;", ""}, {"&omega;", "ω"}, {"&omicron;", "ο"},
{"&oplus;", ""}, {"&or;", ""}, {"&ordf;", "ª"}, {"&ordm;", "º"}, {"&oslash;", "ø"},
{"&otilde;", "õ"}, {"&otimes;", ""}, {"&ouml;", "ö"}, {"&para;", ""}, {"&part;", ""},
{"&permil;", ""}, {"&perp;", ""}, {"&phi;", "φ"}, {"&pi;", "π"}, {"&piv;", "ϖ"},
{"&plusmn;", "±"}, {"&pound;", "£"}, {"&prime;", ""}, {"&prod;", ""}, {"&prop;", ""},
{"&psi;", "ψ"}, {"&quot;", "\""}, {"&radic;", ""}, {"&raquo;", "»"}, {"&rarr;", ""},
{"&rceil;", ""}, {"&rdquo;", "\u201D"}, {"&reg;", "®"}, {"&rfloor;", ""}, {"&rho;", "ρ"},
{"&rlm;", "\u200F"}, {"&rsaquo;", ""}, {"&rsquo;", "\u2019"}, {"&sbquo;", ""}, {"&scaron;", "š"},
{"&sdot;", ""}, {"&sect;", "§"}, {"&shy;", "\xC2\xAD"}, {"&sigma;", "σ"}, {"&sigmaf;", "ς"},
{"&sim;", ""}, {"&spades;", ""}, {"&sub;", ""}, {"&sube;", ""}, {"&sum;", ""},
{"&sup1;", "¹"}, {"&sup2;", "²"}, {"&sup3;", "³"}, {"&sup;", ""}, {"&supe;", ""},
{"&szlig;", "ß"}, {"&tau;", "τ"}, {"&there4;", ""}, {"&theta;", "θ"}, {"&thetasym;", "ϑ"},
{"&thinsp;", " "}, {"&thorn;", "þ"}, {"&tilde;", "˜"}, {"&times;", "×"}, {"&trade;", ""},
{"&uacute;", "ú"}, {"&uarr;", ""}, {"&ucirc;", "û"}, {"&ugrave;", "ù"}, {"&uml;", "¨"},
{"&upsih;", "ϒ"}, {"&upsilon;", "υ"}, {"&uuml;", "ü"}, {"&xi;", "ξ"}, {"&yacute;", "ý"},
{"&yen;", "¥"}, {"&yuml;", "ÿ"}, {"&zeta;", "ζ"}, {"&zwj;", "\u200D"}, {"&zwnj;", "\u200C"},
};
static const size_t ENTITY_LOOKUP_COUNT = sizeof(ENTITY_LOOKUP) / sizeof(ENTITY_LOOKUP[0]);
// Lookup a single HTML entity and return its UTF-8 value
const char* lookupHtmlEntity(const char* entity, int len) {
for (size_t i = 0; i < ENTITY_LOOKUP_COUNT; i++) {
const char* key = ENTITY_LOOKUP[i].key;
// Verify the table is sorted at compile time.
static constexpr int constexprStrcmp(const char* a, const char* b) {
for (size_t i = 0;; i++) {
if (a[i] != b[i]) return (unsigned char)a[i] < (unsigned char)b[i] ? -1 : 1;
if (a[i] == '\0') return 0;
}
}
static constexpr bool isTableSorted() {
for (size_t i = 1; i < ENTITY_LOOKUP_COUNT; i++) {
if (constexprStrcmp(ENTITY_LOOKUP[i - 1].key, ENTITY_LOOKUP[i].key) >= 0) return false;
}
return true;
}
static_assert(isTableSorted(), "ENTITY_LOOKUP must be sorted lexicographically by key");
// Lookup a single HTML entity and return its UTF-8 value.
const char* lookupHtmlEntity(const char* entity, size_t len) {
if (entity == nullptr || len == 0) return nullptr;
size_t lo = 0;
size_t hi = ENTITY_LOOKUP_COUNT;
while (lo < hi) {
const size_t mid = lo + (hi - lo) / 2;
const char* key = ENTITY_LOOKUP[mid].key;
const size_t keyLen = strlen(key);
if (static_cast<size_t>(len) == keyLen && memcmp(entity, key, keyLen) == 0) {
return ENTITY_LOOKUP[i].value;
const size_t cmpLen = (len < keyLen) ? len : keyLen;
int cmp = memcmp(entity, key, cmpLen);
if (cmp == 0) {
// safety net: if prefix equal, shorter string is considered smaller
if (len < keyLen)
cmp = -1;
else if (len > keyLen)
cmp = 1;
else
cmp = 0;
}
if (cmp == 0) return ENTITY_LOOKUP[mid].value;
if (cmp < 0)
hi = mid;
else
lo = mid + 1;
}
return nullptr; // Entity not found
return nullptr;
}
+2 -2
View File
@@ -1,4 +1,4 @@
// from
// based on
// https://github.com/atomic14/diy-esp32-epub-reader/blob/2c2f57fdd7e2a788d14a0bcb26b9e845a47aac42/lib/Epub/RubbishHtmlParser/htmlEntities.cpp
#pragma once
@@ -6,4 +6,4 @@
// Lookup a single HTML entity (including & and ;) and return its UTF-8 value
// Returns nullptr if entity is not found
const char* lookupHtmlEntity(const char* entity, int len);
const char* lookupHtmlEntity(const char* entity, size_t len);
+111 -1
View File
@@ -49,6 +49,24 @@ bool matches(const char* tag_name, const char* possible_tags[], const int possib
return false;
}
const char* getAttribute(const XML_Char** atts, const char* attrName) {
if (!atts) return nullptr;
for (int i = 0; atts[i]; i += 2) {
if (strcmp(atts[i], attrName) == 0) return atts[i + 1];
}
return nullptr;
}
bool isInternalEpubLink(const char* href) {
if (!href || href[0] == '\0') return false;
if (strncmp(href, "http://", 7) == 0 || strncmp(href, "https://", 8) == 0) return false;
if (strncmp(href, "mailto:", 7) == 0) return false;
if (strncmp(href, "ftp://", 6) == 0) return false;
if (strncmp(href, "tel:", 4) == 0) return false;
if (strncmp(href, "javascript:", 11) == 0) return false;
return true;
}
bool isHeaderOrBlock(const char* name) {
return matches(name, HEADER_TAGS, NUM_HEADER_TAGS) || matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS);
}
@@ -121,6 +139,7 @@ void ChapterHtmlSlimParser::startNewTextBlock(const BlockStyle& blockStyle) {
makePages();
}
currentTextBlock.reset(new ParsedText(extraParagraphSpacing, hyphenationEnabled, blockStyle));
wordsExtractedInBlock = 0;
}
void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
@@ -430,6 +449,50 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
}
}
// Detect internal <a href="..."> links (footnotes, cross-references)
// Note: <aside epub:type="footnote"> elements are rendered as normal content
// without special handling. Links pointing to them are collected as footnotes.
if (strcmp(name, "a") == 0) {
const char* href = getAttribute(atts, "href");
bool isInternalLink = isInternalEpubLink(href);
// Special case: javascript:void(0) links with data attributes
// Example: <a href="javascript:void(0)"
// data-xyz="{&quot;name&quot;:&quot;OPS/ch2.xhtml&quot;,&quot;frag&quot;:&quot;id46&quot;}">
if (href && strncmp(href, "javascript:", 11) == 0) {
isInternalLink = false;
// TODO: Parse data-* attributes to extract actual href
}
if (isInternalLink) {
// Flush buffer before style change
if (self->partWordBufferIndex > 0) {
self->flushPartWordBuffer();
self->nextWordContinues = true;
}
self->insideFootnoteLink = true;
self->footnoteLinkDepth = self->depth;
strncpy(self->currentFootnoteLinkHref, href, sizeof(self->currentFootnoteLinkHref) - 1);
self->currentFootnoteLinkHref[sizeof(self->currentFootnoteLinkHref) - 1] = '\0';
self->currentFootnoteLinkText[0] = '\0';
self->currentFootnoteLinkTextLen = 0;
// Apply underline style to visually indicate the link
self->underlineUntilDepth = std::min(self->underlineUntilDepth, self->depth);
StyleStackEntry entry;
entry.depth = self->depth;
entry.hasUnderline = true;
entry.underline = true;
self->inlineStyleStack.push_back(entry);
self->updateEffectiveInlineStyle();
// Skip CSS resolution — we already handled styling for this <a> tag
self->depth += 1;
return;
}
}
// Compute CSS style for this element
CssStyle cssStyle;
if (self->cssParser) {
@@ -582,6 +645,19 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
return;
}
// Collect footnote link display text (for the number label)
// Skip whitespace and brackets to normalize noterefs like "[1]" → "1"
if (self->insideFootnoteLink) {
for (int i = 0; i < len; i++) {
unsigned char c = static_cast<unsigned char>(s[i]);
if (isWhitespace(c) || c == '[' || c == ']') continue;
if (self->currentFootnoteLinkTextLen < static_cast<int>(sizeof(self->currentFootnoteLinkText)) - 1) {
self->currentFootnoteLinkText[self->currentFootnoteLinkTextLen++] = c;
self->currentFootnoteLinkText[self->currentFootnoteLinkTextLen] = '\0';
}
}
}
for (int i = 0; i < len; i++) {
if (isWhitespace(s[i])) {
// Currently looking at whitespace, if there's anything in the partWordBuffer, flush it
@@ -685,7 +761,7 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
void XMLCALL ChapterHtmlSlimParser::defaultHandlerExpand(void* userData, const XML_Char* s, const int len) {
// Check if this looks like an entity reference (&...;)
if (len >= 3 && s[0] == '&' && s[len - 1] == ';') {
const char* utf8Value = lookupHtmlEntity(s, len);
const char* utf8Value = lookupHtmlEntity(s, static_cast<size_t>(len));
if (utf8Value != nullptr) {
// Known entity: expand to its UTF-8 value
characterData(userData, utf8Value, strlen(utf8Value));
@@ -743,6 +819,21 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
self->depth -= 1;
// Closing a footnote link — create entry from collected text and href
if (self->insideFootnoteLink && self->depth == self->footnoteLinkDepth) {
if (self->currentFootnoteLinkText[0] != '\0' && self->currentFootnoteLinkHref[0] != '\0') {
FootnoteEntry entry;
strncpy(entry.number, self->currentFootnoteLinkText, sizeof(entry.number) - 1);
entry.number[sizeof(entry.number) - 1] = '\0';
strncpy(entry.href, self->currentFootnoteLinkHref, sizeof(entry.href) - 1);
entry.href[sizeof(entry.href) - 1] = '\0';
int wordIndex =
self->wordsExtractedInBlock + (self->currentTextBlock ? static_cast<int>(self->currentTextBlock->size()) : 0);
self->pendingFootnotes.push_back({wordIndex, entry});
}
self->insideFootnoteLink = false;
}
// Leaving skip
if (self->skipUntilDepth == self->depth) {
self->skipUntilDepth = INT_MAX;
@@ -910,6 +1001,15 @@ void ChapterHtmlSlimParser::addLineToPage(std::shared_ptr<TextBlock> line) {
currentPageNextY = 0;
}
// Track cumulative words to assign footnotes to the page containing their anchor
wordsExtractedInBlock += line->wordCount();
auto footnoteIt = pendingFootnotes.begin();
while (footnoteIt != pendingFootnotes.end() && footnoteIt->first <= wordsExtractedInBlock) {
currentPage->addFootnote(footnoteIt->second.number, footnoteIt->second.href);
++footnoteIt;
}
pendingFootnotes.erase(pendingFootnotes.begin(), footnoteIt);
// Apply horizontal left inset (margin + padding) as x position offset
const int16_t xOffset = line->getBlockStyle().leftInset();
currentPage->elements.push_back(std::make_shared<PageLine>(line, xOffset, currentPageNextY));
@@ -947,6 +1047,16 @@ void ChapterHtmlSlimParser::makePages() {
renderer, fontId, effectiveWidth,
[this](const std::shared_ptr<TextBlock>& textBlock) { addLineToPage(textBlock); });
// Fallback: transfer any remaining pending footnotes to current page.
// Normally addLineToPage handles this via word-index tracking, but this catches
// edge cases where a footnote's word index equals the exact block size.
if (!pendingFootnotes.empty() && currentPage) {
for (const auto& [idx, fn] : pendingFootnotes) {
currentPage->addFootnote(fn.number, fn.href);
}
pendingFootnotes.clear();
}
// Apply bottom spacing after the paragraph (stored in pixels)
if (blockStyle.marginBottom > 0) {
currentPageNextY += blockStyle.marginBottom;
@@ -5,7 +5,9 @@
#include <climits>
#include <functional>
#include <memory>
#include <vector>
#include "../FootnoteEntry.h"
#include "../ParsedText.h"
#include "../blocks/ImageBlock.h"
#include "../blocks/TextBlock.h"
@@ -66,6 +68,15 @@ class ChapterHtmlSlimParser {
int tableRowIndex = 0;
int tableColIndex = 0;
// Footnote link tracking
bool insideFootnoteLink = false;
int footnoteLinkDepth = -1;
char currentFootnoteLinkText[24] = {};
int currentFootnoteLinkTextLen = 0;
char currentFootnoteLinkHref[64] = {};
std::vector<std::pair<int, FootnoteEntry>> pendingFootnotes; // <wordIndex, entry>
int wordsExtractedInBlock = 0;
void updateEffectiveInlineStyle();
void startNewTextBlock(const BlockStyle& blockStyle);
void flushPartWordBuffer();
+320
View File
@@ -0,0 +1,320 @@
_language_name: "Dansk"
_language_code: "DA"
_order: "15"
STR_CROSSPOINT: "CrossPoint"
STR_BOOTING: "STARTER"
STR_SLEEPING: "HVILE"
STR_ENTERING_SLEEP: "Går i hvile"
STR_BROWSE_FILES: "Gennemsøg filer"
STR_FILE_TRANSFER: "Filoverførelse"
STR_SETTINGS_TITLE: "Indstillinger"
STR_CALIBRE_LIBRARY: "Calibre bibliotek"
STR_CONTINUE_READING: "Fortsæt med at læse"
STR_NO_OPEN_BOOK: "Ingen åben bog"
STR_START_READING: "Start læsning nedenfor"
STR_BOOKS: "Bøger"
STR_NO_BOOKS_FOUND: "Ingen bøger fundet"
STR_SELECT_CHAPTER: "Vælg kapitel"
STR_NO_CHAPTERS: "Ingen kapitler"
STR_END_OF_BOOK: "Bogen er færdig"
STR_EMPTY_CHAPTER: "Tomt kapitel"
STR_INDEXING: "Indekserer"
STR_MEMORY_ERROR: "Hukommelsesfejl"
STR_PAGE_LOAD_ERROR: "Fejl ved sideindlæsning"
STR_EMPTY_FILE: "Tom fil"
STR_OUT_OF_BOUNDS: "Uden for grænsen"
STR_LOADING: "Indlæser..."
STR_LOADING_POPUP: "Indlæser"
STR_LOAD_XTC_FAILED: "Mislykkedes at indlæse XTC"
STR_LOAD_TXT_FAILED: "Mislykkedes at indlæse TXT"
STR_LOAD_EPUB_FAILED: "Mislykkedes at indlæse EPUB"
STR_SD_CARD_ERROR: "SD kort fejl"
STR_WIFI_NETWORKS: "Trådløse netværk"
STR_NO_NETWORKS: "Intet netværk fundet"
STR_NETWORKS_FOUND: "%zu netværk fundet"
STR_SCANNING: "Skanner..."
STR_CONNECTING: "Forbinder..."
STR_CONNECTED: "Forbundet!"
STR_CONNECTION_FAILED: "Forbindelsen mislykkedes"
STR_CONNECTION_TIMEOUT: "Forbindelses timeout"
STR_FORGET_NETWORK: "Glem netværk?"
STR_SAVE_PASSWORD: "Gem adgangskode til næste gang?"
STR_REMOVE_PASSWORD: "Fjern gemt adgangskode?"
STR_PRESS_OK_SCAN: "Tryk OK for at scanne igen"
STR_PRESS_ANY_CONTINUE: "Tryk på en knap for at fortsætte"
STR_SELECT_HINT: "VENSTRE/HØJRE: Vælg | OK: Bekræft"
STR_HOW_CONNECT: "Hvordan vil du oprette forbindelse?"
STR_JOIN_NETWORK: "Tilslut netværk"
STR_CREATE_HOTSPOT: "Opret Hotspot"
STR_JOIN_DESC: "Opret forbindelse til et eksisterende WiFi-netværk"
STR_HOTSPOT_DESC: "Opret et WiFi-netværk andre kan tilslutte sig"
STR_STARTING_HOTSPOT: "Starter Hotspot..."
STR_HOTSPOT_MODE: "Hotspot-tilstand"
STR_CONNECT_WIFI_HINT: "Opret forbindelse fra din enhed til dette WiFi-netværk"
STR_OPEN_URL_HINT: "Åbn denne URL i din browser"
STR_OR_HTTP_PREFIX: "eller http://"
STR_SCAN_QR_HINT: "eller scan QR-kode med din telefon:"
STR_CALIBRE_WIRELESS: "Calibre Wireless"
STR_CALIBRE_WEB_URL: "Calibre Web URL"
STR_CONNECT_WIRELESS: "Opret forbindelse som trådløs enhed"
STR_NETWORK_LEGEND: "* = Krypteret | + = Gemt"
STR_MAC_ADDRESS: "MAC-adresse:"
STR_CHECKING_WIFI: "Tjekker WiFi..."
STR_ENTER_WIFI_PASSWORD: "Indtast WiFi-adgangskode"
STR_ENTER_TEXT: "Indtast tekst"
STR_TO_PREFIX: "til "
STR_CALIBRE_DISCOVERING: "Opdager Calibre..."
STR_CALIBRE_CONNECTING_TO: "Forbinder til "
STR_CALIBRE_CONNECTED_TO: "Forbundet til "
STR_CALIBRE_WAITING_COMMANDS: "Venter på kommandoer..."
STR_CONNECTION_FAILED_RETRYING: "(Forbindelsen mislykkedes, prøver igen)"
STR_CALIBRE_DISCONNECTED: "Calibre afbrudt"
STR_CALIBRE_WAITING_TRANSFER: "Venter på overførelse..."
STR_CALIBRE_TRANSFER_HINT: "Hvis overførslen mislykkes, aktiver\\n'Ignorer ledig plads' i Calibres\\nSmartDevice-plugin-indstillinger."
STR_CALIBRE_RECEIVING: "Modtager: "
STR_CALIBRE_RECEIVED: "Modtaget: "
STR_CALIBRE_WAITING_MORE: "Venter på mere..."
STR_CALIBRE_FAILED_CREATE_FILE: "Kunne ikke oprette fil"
STR_CALIBRE_PASSWORD_REQUIRED: "Adgangskode påkrævet"
STR_CALIBRE_TRANSFER_INTERRUPTED: "Overførelse afbrudt"
STR_CALIBRE_INSTRUCTION_1: "1) Installer CrossPoint Reader-plugin"
STR_CALIBRE_INSTRUCTION_2: "2) Vær på det samme WiFi-netværk"
STR_CALIBRE_INSTRUCTION_3: "3) I Calibre: \"Send til enhed\""
STR_CALIBRE_INSTRUCTION_4: "\"Hold denne skærm åben under afsendelse\""
STR_CAT_DISPLAY: "Skærm"
STR_CAT_READER: "Læser"
STR_CAT_CONTROLS: "Brugerflade"
STR_CAT_SYSTEM: "System"
STR_SLEEP_SCREEN: "Hvile-skærm"
STR_SLEEP_COVER_MODE: "Hvile-skærm omslag-tilstand"
STR_STATUS_BAR: "Statuslinje"
STR_HIDE_BATTERY: "Skjul batteri %"
STR_EXTRA_SPACING: "Ekstra afsnitsafstand"
STR_TEXT_AA: "Tekst Anti-Aliasing"
STR_SHORT_PWR_BTN: "Kort tryk på tænd/sluk-knap"
STR_ORIENTATION: "Læseretning"
STR_FRONT_BTN_LAYOUT: "Knaplayout foran"
STR_SIDE_BTN_LAYOUT: "Knaplayout på siden (læser)"
STR_LONG_PRESS_SKIP: "Langt tryk spring kapitel over"
STR_FONT_FAMILY: "Læser skrifttype"
STR_EXT_READER_FONT: "Ekstern læserskrifttype"
STR_EXT_CHINESE_FONT: "Læserskrifttype"
STR_EXT_UI_FONT: "Brugergrænseflade skrifttype"
STR_FONT_SIZE: "Brugergrænseflade skriftstørrelse"
STR_LINE_SPACING: "Linjeafstand"
STR_ASCII_LETTER_SPACING: "ASCII bogstavafstand"
STR_ASCII_DIGIT_SPACING: "ASCII cifreafstand"
STR_CJK_SPACING: "CJK afstand"
STR_COLOR_MODE: "Farvetilstand"
STR_SCREEN_MARGIN: "Skærmmargen"
STR_PARA_ALIGNMENT: "Afsnitsjustering"
STR_HYPHENATION: "Orddeling"
STR_TIME_TO_SLEEP: "Tid til hvile"
STR_REFRESH_FREQ: "Opdateringsfrekvens"
STR_CALIBRE_SETTINGS: "Calibre-indstillinger"
STR_KOREADER_SYNC: "KOReader Sync"
STR_CHECK_UPDATES: "Søg efter opdateringer"
STR_LANGUAGE: "Sprog"
STR_SELECT_WALLPAPER: "Vælg baggrundsbillede"
STR_CLEAR_READING_CACHE: "Ryd læsecache"
STR_CALIBRE: "Calibre"
STR_USERNAME: "Brugernavn"
STR_PASSWORD: "Adgangskode"
STR_SYNC_SERVER_URL: "Synkroniseringsserver-URL"
STR_DOCUMENT_MATCHING: "Dokumentsammenkobling"
STR_AUTHENTICATE: "Godkend"
STR_KOREADER_USERNAME: "KOReader brugernavn"
STR_KOREADER_PASSWORD: "KOReader adgangskode"
STR_FILENAME: "Filnavn"
STR_BINARY: "Binær"
STR_SET_CREDENTIALS_FIRST: "Angiv legitimationsoplysninger først"
STR_WIFI_CONN_FAILED: "WiFi-forbindelsen mislykkedes"
STR_AUTHENTICATING: "Godkender..."
STR_AUTH_SUCCESS: "Godkendt!"
STR_KOREADER_AUTH: "KOReader-godkendelse"
STR_SYNC_READY: "KOReader-synkronisering er klar til brug"
STR_AUTH_FAILED: "Godkendelse mislykkedes"
STR_DONE: "Færdig"
STR_CLEAR_CACHE_WARNING_1: "Dette vil rydde alle cachelagrede bogdata."
STR_CLEAR_CACHE_WARNING_2: "Al læsefremskridt vil gå tabt!"
STR_CLEAR_CACHE_WARNING_3: "Bøger skal indekseres igen"
STR_CLEAR_CACHE_WARNING_4: "når de åbnes igen."
STR_CLEARING_CACHE: "Rydder cache..."
STR_CACHE_CLEARED: "Cache ryddet"
STR_ITEMS_REMOVED: "elementer fjernet"
STR_FAILED_LOWER: "mislykkedes"
STR_CLEAR_CACHE_FAILED: "Kunne ikke rydde cache"
STR_CHECK_SERIAL_OUTPUT: "Tjek serielt output for detaljer"
STR_DARK: "Mørk"
STR_LIGHT: "Lys"
STR_CUSTOM: "Brugerdefineret"
STR_COVER: "Omslag"
STR_NONE_OPT: "Ingen"
STR_FIT: "Tilpas"
STR_CROP: "Beskær"
STR_NO_PROGRESS: "Ingen fremskridt"
STR_FULL_OPT: "Fuld"
STR_NEVER: "Aldrig"
STR_IN_READER: "I læseren"
STR_ALWAYS: "Altid"
STR_IGNORE: "Ignorer"
STR_SLEEP: "Hvile"
STR_PAGE_TURN: "Sideskift"
STR_PORTRAIT: "Portræt"
STR_LANDSCAPE_CW: "Liggende med uret"
STR_INVERTED: "Inverteret"
STR_LANDSCAPE_CCW: "Liggende mod uret"
STR_FRONT_LAYOUT_BCLR: "Bck, Cnfrm, Lft, Rght"
STR_FRONT_LAYOUT_LRBC: "Lft, Rght, Bck, Cnfrm"
STR_FRONT_LAYOUT_LBCR: "Lft, Bck, Cnfrm, Rght"
STR_PREV_NEXT: "Forrige/Næste"
STR_NEXT_PREV: "Næste/Forrige"
STR_BOOKERLY: "Bookerly"
STR_NOTO_SANS: "Noto Sans"
STR_OPEN_DYSLEXIC: "Open Dyslexic"
STR_SMALL: "Lille"
STR_MEDIUM: "Mellem"
STR_LARGE: "Stor"
STR_X_LARGE: "Ekstra stor"
STR_TIGHT: "Tæt"
STR_NORMAL: "Normal"
STR_WIDE: "Bred"
STR_JUSTIFY: "Justeret"
STR_ALIGN_LEFT: "Venstre"
STR_CENTER: "Centreret"
STR_ALIGN_RIGHT: "Højre"
STR_MIN_1: "1 min"
STR_MIN_5: "5 min"
STR_MIN_10: "10 min"
STR_MIN_15: "15 min"
STR_MIN_30: "30 min"
STR_PAGES_1: "1 side"
STR_PAGES_5: "5 sider"
STR_PAGES_10: "10 sider"
STR_PAGES_15: "15 sider"
STR_PAGES_30: "30 sider"
STR_UPDATE: "Opdater"
STR_CHECKING_UPDATE: "Søger efter opdatering..."
STR_NEW_UPDATE: "Ny opdatering tilgængelig!"
STR_CURRENT_VERSION: "Nuværende version: "
STR_NEW_VERSION: "Ny version: "
STR_UPDATING: "Opdaterer..."
STR_NO_UPDATE: "Ingen opdatering tilgængelig"
STR_UPDATE_FAILED: "Opdatering mislykkedes"
STR_UPDATE_COMPLETE: "Opdatering færdig"
STR_POWER_ON_HINT: "Hold tænd/sluk-knappen nede for at tænde igen"
STR_EXTERNAL_FONT: "Ekstern skrifttype"
STR_BUILTIN_DISABLED: "Indbygget (deaktiveret)"
STR_NO_ENTRIES: "Ingen poster fundet"
STR_DOWNLOADING: "Downloader..."
STR_DOWNLOAD_FAILED: "Download mislykkedes"
STR_ERROR_MSG: "Fejl:"
STR_UNNAMED: "Unavngivet"
STR_NO_SERVER_URL: "Ingen server-URL konfigureret"
STR_FETCH_FEED_FAILED: "Kunne ikke hente feed"
STR_PARSE_FEED_FAILED: "Kunne ikke fortolke feed"
STR_NETWORK_PREFIX: "Netværk: "
STR_IP_ADDRESS_PREFIX: "IP-adresse: "
STR_SCAN_QR_WIFI_HINT: "eller scan QR-kode med din telefon for at oprette forbindelse til WiFi."
STR_ERROR_GENERAL_FAILURE: "Fejl: Generel fejl"
STR_ERROR_NETWORK_NOT_FOUND: "Fejl: Netværk ikke fundet"
STR_ERROR_CONNECTION_TIMEOUT: "Fejl: Forbindelses timeout"
STR_SD_CARD: "SD-kort"
STR_BACK: "« Tilbage"
STR_EXIT: "« Afslut"
STR_HOME: "« Hjem"
STR_SAVE: "« Gem"
STR_SELECT: "Vælg"
STR_TOGGLE: "Skift"
STR_CONFIRM: "Bekræft"
STR_CANCEL: "Annuller"
STR_CONNECT: "Forbind"
STR_OPEN: "Åbn"
STR_DOWNLOAD: "Download"
STR_RETRY: "Prøv igen"
STR_YES: "Ja"
STR_NO: "Nej"
STR_STATE_ON: "TÆNDT"
STR_STATE_OFF: "SLUKKET"
STR_SET: "Indstil"
STR_NOT_SET: "Ikke indstillet"
STR_DIR_LEFT: "Venstre"
STR_DIR_RIGHT: "Højre"
STR_DIR_UP: "Op"
STR_DIR_DOWN: "Ned"
STR_CAPS_ON: "CAPS"
STR_CAPS_OFF: "caps"
STR_OK_BUTTON: "OK"
STR_ON_MARKER: "[ON]"
STR_SLEEP_COVER_FILTER: "Hvile-skærm omslag-filter"
STR_FILTER_CONTRAST: "Kontrast"
STR_STATUS_BAR_FULL_PERCENT: "Fuld m/ procent"
STR_STATUS_BAR_FULL_BOOK: "Fuld m/ boglinje"
STR_STATUS_BAR_BOOK_ONLY: "Kun boglinje"
STR_STATUS_BAR_FULL_CHAPTER: "Fuld m/ kapitellinje"
STR_UI_THEME: "Brugergrænseflade tema"
STR_THEME_CLASSIC: "Klassisk"
STR_THEME_LYRA: "Lyra"
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
STR_SUNLIGHT_FADING_FIX: "Sollysfading-rettelse"
STR_REMAP_FRONT_BUTTONS: "Omtildel frontknapper"
STR_OPDS_BROWSER: "OPDS Browser"
STR_COVER_CUSTOM: "Omslag + Brugerdefineret"
STR_RECENTS: "Seneste"
STR_MENU_RECENT_BOOKS: "Seneste bøger"
STR_NO_RECENT_BOOKS: "Ingen seneste bøger"
STR_CALIBRE_DESC: "Brug Calibre trådløs enhedsoverførelse"
STR_FORGET_AND_REMOVE: "Glem netværk og fjern gemt adgangskode?"
STR_FORGET_BUTTON: "Glem"
STR_CALIBRE_STARTING: "Starter Calibre..."
STR_CALIBRE_SETUP: "Opsætning"
STR_CALIBRE_STATUS: "Status"
STR_CLEAR_BUTTON: "Ryd"
STR_DEFAULT_VALUE: "Standard"
STR_REMAP_PROMPT: "Tryk på en frontknap for hver rolle"
STR_UNASSIGNED: "Ikke tildelt"
STR_ALREADY_ASSIGNED: "Allerede tildelt"
STR_REMAP_RESET_HINT: "Sideknap op: Nulstil til standardlayout"
STR_REMAP_CANCEL_HINT: "Sideknap ned: Annuller omtildeling"
STR_HW_BACK_LABEL: "Tilbage (1. knap)"
STR_HW_CONFIRM_LABEL: "Bekræft (2. knap)"
STR_HW_LEFT_LABEL: "Venstre (3. knap)"
STR_HW_RIGHT_LABEL: "Højre (4. knap)"
STR_GO_TO_PERCENT: "Gå til %"
STR_GO_HOME_BUTTON: "Gå til start"
STR_SYNC_PROGRESS: "Synkroniser fremskridt"
STR_DELETE_CACHE: "Slet bogcache"
STR_CHAPTER_PREFIX: "Kapitel: "
STR_PAGES_SEPARATOR: " sider | "
STR_BOOK_PREFIX: "Bog: "
STR_KBD_SHIFT: "shift"
STR_KBD_SHIFT_CAPS: "SHIFT"
STR_KBD_LOCK: "LOCK"
STR_CALIBRE_URL_HINT: "Tilføj /opds til din URL for Calibre"
STR_PERCENT_STEP_HINT: "Venstre/Højre: 1% Op/Ned: 10%"
STR_SYNCING_TIME: "Synkroniserer tid..."
STR_CALC_HASH: "Beregner dokument-hash..."
STR_HASH_FAILED: "Kunne ikke beregne dokument-hash"
STR_FETCH_PROGRESS: "Henter fjernfremskridt..."
STR_UPLOAD_PROGRESS: "Uploader fremskridt..."
STR_NO_CREDENTIALS_MSG: "Ingen legitimationsoplysninger konfigureret"
STR_KOREADER_SETUP_HINT: "Opsæt KOReader-konto i Indstillinger"
STR_PROGRESS_FOUND: "Fremskridt fundet!"
STR_REMOTE_LABEL: "Ekstern:"
STR_LOCAL_LABEL: "Lokal:"
STR_PAGE_OVERALL_FORMAT: "Side %d, %.2f%% samlet"
STR_PAGE_TOTAL_OVERALL_FORMAT: "Side %d/%d, %.2f%% samlet"
STR_DEVICE_FROM_FORMAT: " Fra: %s"
STR_APPLY_REMOTE: "Anvend fjernfremskridt"
STR_UPLOAD_LOCAL: "Upload lokalt fremskridt"
STR_NO_REMOTE_MSG: "Ingen fjernfremskridt fundet"
STR_UPLOAD_PROMPT: "Upload nuværende position?"
STR_UPLOAD_SUCCESS: "Fremskridt uploadet!"
STR_SYNC_FAILED_MSG: "Synkronisering mislykkedes"
STR_SECTION_PREFIX: "Afsnit "
STR_UPLOAD: "Upload"
STR_BOOK_S_STYLE: "Bogens stil"
STR_EMBEDDED_STYLE: "Indlejret stil"
STR_OPDS_SERVER_URL: "OPDS Server URL"
STR_SCREENSHOT_BUTTON: "Tag skærmbillede"
+4 -1
View File
@@ -331,4 +331,7 @@ STR_UPLOAD: "Upload"
STR_BOOK_S_STYLE: "Book's Style"
STR_EMBEDDED_STYLE: "Embedded Style"
STR_OPDS_SERVER_URL: "OPDS Server URL"
STR_SCREENSHOT_BUTTON: "Take screenshot"
STR_FOOTNOTES: "Footnotes"
STR_NO_FOOTNOTES: "No footnotes on this page"
STR_LINK: "[link]"
STR_SCREENSHOT_BUTTON: "Take screenshot"
+24 -24
View File
@@ -63,7 +63,7 @@ STR_MAC_ADDRESS: "Adres MAC:"
STR_CHECKING_WIFI: "Sprawdzanie WiFi..."
STR_ENTER_WIFI_PASSWORD: "Wprowadź hasło WiFi"
STR_ENTER_TEXT: "Wprowadź tekst"
STR_TO_PREFIX: "do "
STR_TO_PREFIX: "Z "
STR_CALIBRE_DISCOVERING: "Odkrywanie Calibre..."
STR_CALIBRE_CONNECTING_TO: "Łączenie do "
STR_CALIBRE_CONNECTED_TO: "Podłączony do "
@@ -87,21 +87,21 @@ STR_CAT_READER: "Czytnik"
STR_CAT_CONTROLS: "Sterowanie"
STR_CAT_SYSTEM: "System"
STR_SLEEP_SCREEN: "Wygaszacz ekranu"
STR_SLEEP_COVER_MODE: "Tryb ekranu uśpienia z okładką"
STR_SLEEP_COVER_MODE: "Okładki wygaszacza"
STR_STATUS_BAR: "Status Bar"
STR_HIDE_BATTERY: "Ukryj % baterii"
STR_EXTRA_SPACING: "Dodatkowe odstępy paragrafów"
STR_TEXT_AA: "Wygładzanie tekstu"
STR_SHORT_PWR_BTN: "Krótkie naciśnięcie przycisku zasilania"
STR_SHORT_PWR_BTN: "Krótkie naciśnięcie zasilania"
STR_ORIENTATION: "Układ czytania"
STR_FRONT_BTN_LAYOUT: "Układ przednich przycisków"
STR_SIDE_BTN_LAYOUT: "Układ przycisków bocznych(czytnik)"
STR_SIDE_BTN_LAYOUT: "Układ przycisków bocznych"
STR_LONG_PRESS_SKIP: "Przytrzymaj aby przeskoczyć rozdział"
STR_FONT_FAMILY: "Czcionka"
STR_EXT_READER_FONT: "Zewnętrzna czcionka czytnika"
STR_EXT_CHINESE_FONT: "Czcionka czytnika"
STR_EXT_UI_FONT: "Czcionka UI"
STR_FONT_SIZE: "Rozmiar czcionki UI"
STR_FONT_SIZE: "Rozmiar czcionki"
STR_LINE_SPACING: "Odstępy między wierszami"
STR_ASCII_LETTER_SPACING: "Odstępy liter ASCII"
STR_ASCII_DIGIT_SPACING: "Odstępy cyfr ASCII"
@@ -160,7 +160,7 @@ STR_IN_READER: "W czytniku"
STR_ALWAYS: "Zawsze"
STR_IGNORE: "Ignoruj"
STR_SLEEP: "Uśpienie"
STR_PAGE_TURN: "Obrót strony"
STR_PAGE_TURN: "Nast. str."
STR_PORTRAIT: "Pionowo"
STR_LANDSCAPE_CW: "Poziomo L"
STR_INVERTED: "Odwrócony"
@@ -168,8 +168,8 @@ STR_LANDSCAPE_CCW: "Poziomo P"
STR_FRONT_LAYOUT_BCLR: "Wstecz, Potwierdź, Lewo, Prawo"
STR_FRONT_LAYOUT_LRBC: "Lewo, Prawo, Wstecz, Potwierdź"
STR_FRONT_LAYOUT_LBCR: "Lewo, Wstecz, Potwierdź, Prawo"
STR_PREV_NEXT: "Poprzedni/Następny"
STR_NEXT_PREV: "Następny/Poprzedni"
STR_PREV_NEXT: "Poprz./Nast."
STR_NEXT_PREV: "Nast./Poprz."
STR_BOOKERLY: "Bookerly"
STR_NOTO_SANS: "Noto Sans"
STR_OPEN_DYSLEXIC: "Open Dyslexic"
@@ -177,13 +177,13 @@ STR_SMALL: "Mały"
STR_MEDIUM: "Średni"
STR_LARGE: "Duży"
STR_X_LARGE: "B. duży"
STR_TIGHT: "Ciasno"
STR_NORMAL: "Normalnie"
STR_WIDE: "Szeroko"
STR_JUSTIFY: "Wyrównanie"
STR_ALIGN_LEFT: "Lewej"
STR_CENTER: "Środkuj"
STR_ALIGN_RIGHT: "Prawej"
STR_TIGHT: "Małe"
STR_NORMAL: "Normalne"
STR_WIDE: "Duże"
STR_JUSTIFY: "Wyrównane"
STR_ALIGN_LEFT: "Lewo"
STR_CENTER: "Środek"
STR_ALIGN_RIGHT: "Prawo"
STR_MIN_1: "1 min"
STR_MIN_5: "5 min"
STR_MIN_10: "10 min"
@@ -220,13 +220,13 @@ STR_SCAN_QR_WIFI_HINT: "albo skanuj kod QR telefonem aby połączyć się do Wif
STR_ERROR_GENERAL_FAILURE: "Błąd: Ogólny"
STR_ERROR_NETWORK_NOT_FOUND: "Błąd: Sieci nie znaleziono"
STR_ERROR_CONNECTION_TIMEOUT: "Błąd: Przekroczono limit czasu połączenia"
STR_SD_CARD: "karta S"
STR_SD_CARD: "Karta SD"
STR_BACK: "« Wstecz"
STR_EXIT: "« Wyjdź"
STR_HOME: "« Home"
STR_SAVE: "« Zapisz"
STR_SELECT: "Wybierz"
STR_TOGGLE: "Przełącz"
STR_TOGGLE: "Zmień"
STR_CONFIRM: "Potwierdź"
STR_CANCEL: "Anuluj"
STR_CONNECT: "Połącz"
@@ -237,8 +237,8 @@ STR_YES: "Tak"
STR_NO: "Nie"
STR_SHOW: "Pokaż"
STR_HIDE: "Ukryj"
STR_STATE_ON: "ON"
STR_STATE_OFF: "OFF"
STR_STATE_ON: "Wł."
STR_STATE_OFF: "Wył."
STR_SET: "Ustawiono"
STR_NOT_SET: "Nie ustawiono"
STR_DIR_LEFT: "Lewo"
@@ -249,11 +249,11 @@ STR_CAPS_ON: "CAPS"
STR_CAPS_OFF: "caps"
STR_OK_BUTTON: "OK"
STR_ON_MARKER: "[ON]"
STR_SLEEP_COVER_FILTER: "Filtr okładki uśpionego ekranu"
STR_SLEEP_COVER_FILTER: "Filtr okładek wygaszacza"
STR_FILTER_CONTRAST: "Kontrast"
STR_CUSTOMISE_STATUS_BAR: "Dostosowanie paska statusu"
STR_CHAPTER_PAGE_COUNT: "Strona rozdziału"
STR_BOOK_PROGRESS_PERCENTAGE: "Pasek postępu książki"
STR_BOOK_PROGRESS_PERCENTAGE: "Postęp książki"
STR_PROGRESS_BAR: "Pasek postępu"
STR_PROGRESS_BAR_THICKNESS: "Grubość paska postępu"
STR_PROGRESS_BAR_THIN: "Cienki"
@@ -306,9 +306,9 @@ STR_KBD_SHIFT: "shift"
STR_KBD_SHIFT_CAPS: "SHIFT"
STR_KBD_LOCK: "LOCK"
STR_CALIBRE_URL_HINT: "Dla Calibre, dodaj /opds do adresu URL"
STR_PERCENT_STEP_HINT: "Left/Right: 1% Up/Down: 10%"
STR_PERCENT_STEP_HINT: "Lewo/Prawo: 1% Góra/Dół: 10%"
STR_SYNCING_TIME: "Synchronizacja czasu..."
STR_CALC_HASH: "Obliczanie sumu kontrolnekj..."
STR_CALC_HASH: "Obliczanie sumy kontrolnej..."
STR_HASH_FAILED: "Błąd obliczania sumy kontrolnej"
STR_FETCH_PROGRESS: "Pobieranie zdalnego postępu.."
STR_UPLOAD_PROGRESS: "Postęp wysyłania..."
@@ -329,6 +329,6 @@ STR_SYNC_FAILED_MSG: "Synchronizacja nieudana"
STR_SECTION_PREFIX: "Sekcja "
STR_UPLOAD: "Wyślij"
STR_BOOK_S_STYLE: "Styl książki"
STR_EMBEDDED_STYLE: "Styl wbudowany"
STR_EMBEDDED_STYLE: "Style wbudowane w EPUB"
STR_OPDS_SERVER_URL: "URL serwera OPDS"
STR_SCREENSHOT_BUTTON: "Zrób zrzut ekranu"
+18 -1
View File
@@ -235,6 +235,8 @@ STR_DOWNLOAD: "Descarcă"
STR_RETRY: "Reîncercare"
STR_YES: "Da"
STR_NO: "Nu"
STR_SHOW: "Afișează"
STR_HIDE: "Ascunde"
STR_STATE_ON: "Pornit"
STR_STATE_OFF: "Oprit"
STR_SET: "Setare"
@@ -249,6 +251,21 @@ STR_OK_BUTTON: "OK"
STR_ON_MARKER: "[ON]"
STR_SLEEP_COVER_FILTER: "Filtru ecran de repaus"
STR_FILTER_CONTRAST: "Contrast"
STR_CUSTOMISE_STATUS_BAR: "Customizaţi bara de stare"
STR_CHAPTER_PAGE_COUNT: "Număr de pagini în capitol"
STR_BOOK_PROGRESS_PERCENTAGE: "Progres carte procentual"
STR_PROGRESS_BAR: "Bară de progres"
STR_PROGRESS_BAR_THICKNESS: "Grosime bară de progres"
STR_PROGRESS_BAR_THIN: "Subţire"
STR_PROGRESS_BAR_MEDIUM: "Medie"
STR_PROGRESS_BAR_THICK: "Groasă"
STR_BOOK: "Carte"
STR_CHAPTER: "Capitol"
STR_EXAMPLE_CHAPTER: "Capitolul 21"
STR_EXAMPLE_BOOK: "Titlul cărţii"
STR_PREVIEW: "Previzualizare"
STR_TITLE: "Titlu"
STR_BATTERY: "Baterie"
STR_UI_THEME: "Tema UI"
STR_THEME_CLASSIC: "Clasic"
STR_THEME_LYRA: "Lyra"
@@ -281,7 +298,7 @@ STR_GO_TO_PERCENT: "Săriţi la %"
STR_GO_HOME_BUTTON: "Acasă"
STR_SYNC_PROGRESS: "Progres sincronizare"
STR_DELETE_CACHE: "Ştergere cache cărţi"
STR_CHAPTER_PREFIX: "Capitol: "
STR_DISPLAY_QR: "Afișați pagina ca cod QR"
STR_PAGES_SEPARATOR: " pagini | "
STR_BOOK_PREFIX: "Carte: "
STR_KBD_SHIFT: "shift"
+17
View File
@@ -235,6 +235,8 @@ STR_DOWNLOAD: "Скачать"
STR_RETRY: "Повторить"
STR_YES: "Да"
STR_NO: "Нет"
STR_SHOW: "Показать"
STR_HIDE: "Скрыть"
STR_STATE_ON: "ВКЛ"
STR_STATE_OFF: "ВЫКЛ"
STR_SET: "Установлено"
@@ -249,6 +251,20 @@ STR_OK_BUTTON: "OK"
STR_ON_MARKER: "[ВКЛ]"
STR_SLEEP_COVER_FILTER: "Фильтр экрана сна"
STR_FILTER_CONTRAST: "Контраст"
STR_CUSTOMISE_STATUS_BAR: "Настройка строки состояния"
STR_CHAPTER_PAGE_COUNT: "Количество страниц главы"
STR_BOOK_PROGRESS_PERCENTAGE: "% прочтения книги"
STR_PROGRESS_BAR: "Полоса прогресса"
STR_PROGRESS_BAR_THICKNESS: "Толщина индикатора прогресса"
STR_PROGRESS_BAR_THIN: "Тонкий"
STR_PROGRESS_BAR_MEDIUM: "Средний"
STR_PROGRESS_BAR_THICK: "Толстый"
STR_BOOK: "Книга"
STR_CHAPTER: "Глава"
STR_EXAMPLE_BOOK: "Название книги"
STR_PREVIEW: "Предпросмотр"
STR_TITLE: "Заглавие"
STR_BATTERY: "Батарея"
STR_UI_THEME: "Тема интерфейса"
STR_THEME_CLASSIC: "Классическая"
STR_THEME_LYRA: "Lyra"
@@ -282,6 +298,7 @@ STR_GO_HOME_BUTTON: "На главную"
STR_SYNC_PROGRESS: "Синхронизировать прогресс"
STR_DELETE_CACHE: "Удалить кэш книги"
STR_CHAPTER_PREFIX: "Глава:"
STR_DISPLAY_QR: "Показать страницу в виде QR-кода"
STR_PAGES_SEPARATOR: "стр. |"
STR_BOOK_PREFIX: "Книга:"
STR_KBD_SHIFT: "shift"
+21
View File
@@ -234,6 +234,8 @@ STR_OPEN: "Відкрити"
STR_DOWNLOAD: "Завант."
STR_RETRY: "Повтор."
STR_YES: "Так"
STR_SHOW: "Показати"
STR_HIDE: "Сховати"
STR_NO: "Ні"
STR_STATE_ON: "УВІМК"
STR_STATE_OFF: "ВИМК"
@@ -249,6 +251,21 @@ STR_OK_BUTTON: "OK"
STR_ON_MARKER: "[УВІМК]"
STR_SLEEP_COVER_FILTER: "Фільтр обкладинки екрана сну"
STR_FILTER_CONTRAST: "Контраст"
STR_CUSTOMISE_STATUS_BAR: "Налаштувати рядок стану"
STR_CHAPTER_PAGE_COUNT: "Кількість сторінок розділу"
STR_BOOK_PROGRESS_PERCENTAGE: "Відсоток прочитаного"
STR_PROGRESS_BAR: "Рядок прогресу"
STR_PROGRESS_BAR_THICKNESS: "Товщина рядку прогресу"
STR_PROGRESS_BAR_THIN: "Тонкий"
STR_PROGRESS_BAR_MEDIUM: "Середній"
STR_PROGRESS_BAR_THICK: "Жирний"
STR_BOOK: "Книга"
STR_CHAPTER: "Розділ"
STR_EXAMPLE_CHAPTER: "Розділ 21"
STR_EXAMPLE_BOOK: "Назва книги"
STR_PREVIEW: "Перегляд"
STR_TITLE: "Назва"
STR_BATTERY: "Акумулятор"
STR_UI_THEME: "Тема інтерфейсу"
STR_THEME_CLASSIC: "Класична"
STR_THEME_LYRA: "Lyra"
@@ -281,6 +298,7 @@ STR_GO_TO_PERCENT: "Перейти до %"
STR_GO_HOME_BUTTON: "На головну"
STR_SYNC_PROGRESS: "Прогрес синхронізації"
STR_DELETE_CACHE: "Видалити кеш книги"
STR_DISPLAY_QR: "Показати сторінку як QR-код"
STR_CHAPTER_PREFIX: "Розділ: "
STR_PAGES_SEPARATOR: " сторінок | "
STR_BOOK_PREFIX: "Книга: "
@@ -313,4 +331,7 @@ STR_UPLOAD: "Завантажити"
STR_BOOK_S_STYLE: "Стиль книги"
STR_EMBEDDED_STYLE: "Вбудований стиль"
STR_OPDS_SERVER_URL: "URL сервера OPDS"
STR_FOOTNOTES: "Зноски"
STR_NO_FOOTNOTES: "На цій сторінці немає зносок"
STR_LINK: "[посилання]"
STR_SCREENSHOT_BUTTON: "Знімок екрана"