mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-08 08:37:38 +03:00
Compare commits
No commits in common. "bb5fd0cee2521b68b8c9d945b3cd5489a32e1d85" and "810d4acffd7b6ea8eedf629c44c054bba885c96e" have entirely different histories.
bb5fd0cee2
...
810d4acffd
@ -96,10 +96,6 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
|||||||
- Left, Right, Back, Confirm
|
- Left, Right, Back, Confirm
|
||||||
- Left, Back, Confirm, Right
|
- Left, Back, Confirm, Right
|
||||||
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
||||||
- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skip to the next/previous chapter.
|
|
||||||
- "Chapter Skip" (default) - Long-pressing skips to next/previous chapter
|
|
||||||
- "Page Scroll" - Long-pressing scrolls a page up/down
|
|
||||||
- Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
|
||||||
- **Reader Font Family**: Choose the font used for reading:
|
- **Reader Font Family**: Choose the font used for reading:
|
||||||
- "Bookerly" (default) - Amazon's reading font
|
- "Bookerly" (default) - Amazon's reading font
|
||||||
- "Noto Sans" - Google's sans-serif font
|
- "Noto Sans" - Google's sans-serif font
|
||||||
@ -148,9 +144,6 @@ If the **Short Power Button Click** setting is set to "Page Turn", you can also
|
|||||||
* **Next Chapter:** Press and **hold** the **Right** (or **Volume Down**) button briefly, then release.
|
* **Next Chapter:** Press and **hold** the **Right** (or **Volume Down**) button briefly, then release.
|
||||||
* **Previous Chapter:** Press and **hold** the **Left** (or **Volume Up**) button briefly, then release.
|
* **Previous Chapter:** Press and **hold** the **Left** (or **Volume Up**) button briefly, then release.
|
||||||
|
|
||||||
This feature can be disabled in **[Settings](#35-settings)** to help avoid changing chapters by mistake.
|
|
||||||
|
|
||||||
|
|
||||||
### System Navigation
|
### System Navigation
|
||||||
* **Return to Book Selection:** Press **Back** to close the book and return to the **[Book Selection](#32-book-selection)** screen.
|
* **Return to Book Selection:** Press **Back** to close the book and return to the **[Book Selection](#32-book-selection)** screen.
|
||||||
* **Return to Home:** Press and **hold** the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
|
* **Return to Home:** Press and **hold** the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
|
||||||
|
|||||||
@ -71,7 +71,6 @@ bool isAlphabetic(const uint32_t cp) { return isLatinLetter(cp) || isCyrillicLet
|
|||||||
|
|
||||||
bool isPunctuation(const uint32_t cp) {
|
bool isPunctuation(const uint32_t cp) {
|
||||||
switch (cp) {
|
switch (cp) {
|
||||||
case '-':
|
|
||||||
case '.':
|
case '.':
|
||||||
case ',':
|
case ',':
|
||||||
case '!':
|
case '!':
|
||||||
@ -88,11 +87,8 @@ bool isPunctuation(const uint32_t cp) {
|
|||||||
case 0x2019: // ’
|
case 0x2019: // ’
|
||||||
case 0x201C: // “
|
case 0x201C: // “
|
||||||
case 0x201D: // ”
|
case 0x201D: // ”
|
||||||
case 0x00A0: // no-break space
|
|
||||||
case '{':
|
case '{':
|
||||||
case '}':
|
case '}':
|
||||||
case '[':
|
|
||||||
case ']':
|
|
||||||
case '/':
|
case '/':
|
||||||
case 0x203A: // ›
|
case 0x203A: // ›
|
||||||
case 0x2026: // …
|
case 0x2026: // …
|
||||||
@ -111,6 +107,18 @@ bool isExplicitHyphen(const uint32_t cp) {
|
|||||||
case 0x058A: // Armenian hyphen
|
case 0x058A: // Armenian hyphen
|
||||||
case 0x2010: // hyphen
|
case 0x2010: // hyphen
|
||||||
case 0x2011: // non-breaking hyphen
|
case 0x2011: // non-breaking hyphen
|
||||||
|
case 0x2012: // figure dash
|
||||||
|
case 0x2013: // en dash
|
||||||
|
case 0x2014: // em dash
|
||||||
|
case 0x2015: // horizontal bar
|
||||||
|
case 0x2043: // hyphen bullet
|
||||||
|
case 0x207B: // superscript minus
|
||||||
|
case 0x208B: // subscript minus
|
||||||
|
case 0x2212: // minus sign
|
||||||
|
case 0x2E17: // double oblique hyphen
|
||||||
|
case 0x2E3A: // two-em dash
|
||||||
|
case 0x2E3B: // three-em dash
|
||||||
|
case 0xFE58: // small em dash
|
||||||
case 0xFE63: // small hyphen-minus
|
case 0xFE63: // small hyphen-minus
|
||||||
case 0xFF0D: // fullwidth hyphen-minus
|
case 0xFF0D: // fullwidth hyphen-minus
|
||||||
return true;
|
return true;
|
||||||
@ -121,28 +129,7 @@ bool isExplicitHyphen(const uint32_t cp) {
|
|||||||
|
|
||||||
bool isSoftHyphen(const uint32_t cp) { return cp == 0x00AD; }
|
bool isSoftHyphen(const uint32_t cp) { return cp == 0x00AD; }
|
||||||
|
|
||||||
void trimSurroundingPunctuationAndFootnote(std::vector<CodepointInfo>& cps) {
|
void trimSurroundingPunctuation(std::vector<CodepointInfo>& cps) {
|
||||||
if (cps.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove trailing footnote references like [12], even if punctuation trails after the closing bracket.
|
|
||||||
if (cps.size() >= 3) {
|
|
||||||
int end = static_cast<int>(cps.size()) - 1;
|
|
||||||
while (end >= 0 && isPunctuation(cps[end].value)) {
|
|
||||||
--end;
|
|
||||||
}
|
|
||||||
int pos = end;
|
|
||||||
if (pos >= 0 && isAsciiDigit(cps[pos].value)) {
|
|
||||||
while (pos >= 0 && isAsciiDigit(cps[pos].value)) {
|
|
||||||
--pos;
|
|
||||||
}
|
|
||||||
if (pos >= 0 && cps[pos].value == '[' && end - pos > 1) {
|
|
||||||
cps.erase(cps.begin() + pos, cps.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!cps.empty() && isPunctuation(cps.front().value)) {
|
while (!cps.empty() && isPunctuation(cps.front().value)) {
|
||||||
cps.erase(cps.begin());
|
cps.erase(cps.begin());
|
||||||
}
|
}
|
||||||
@ -165,3 +152,27 @@ std::vector<CodepointInfo> collectCodepoints(const std::string& word) {
|
|||||||
|
|
||||||
return cps;
|
return cps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void trimTrailingFootnoteReference(std::vector<CodepointInfo>& cps) {
|
||||||
|
if (cps.size() < 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int closing = static_cast<int>(cps.size()) - 1;
|
||||||
|
if (cps[closing].value != ']') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int pos = closing - 1;
|
||||||
|
if (pos < 0 || !isAsciiDigit(cps[pos].value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (pos >= 0 && isAsciiDigit(cps[pos].value)) {
|
||||||
|
--pos;
|
||||||
|
}
|
||||||
|
if (pos < 0 || cps[pos].value != '[') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (closing - pos <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cps.erase(cps.begin() + pos, cps.end());
|
||||||
|
}
|
||||||
|
|||||||
@ -21,5 +21,6 @@ bool isPunctuation(uint32_t cp);
|
|||||||
bool isAsciiDigit(uint32_t cp);
|
bool isAsciiDigit(uint32_t cp);
|
||||||
bool isExplicitHyphen(uint32_t cp);
|
bool isExplicitHyphen(uint32_t cp);
|
||||||
bool isSoftHyphen(uint32_t cp);
|
bool isSoftHyphen(uint32_t cp);
|
||||||
void trimSurroundingPunctuationAndFootnote(std::vector<CodepointInfo>& cps);
|
void trimSurroundingPunctuation(std::vector<CodepointInfo>& cps);
|
||||||
std::vector<CodepointInfo> collectCodepoints(const std::string& word);
|
std::vector<CodepointInfo> collectCodepoints(const std::string& word);
|
||||||
|
void trimTrailingFootnoteReference(std::vector<CodepointInfo>& cps);
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
#include "Hyphenator.h"
|
#include "Hyphenator.h"
|
||||||
|
|
||||||
|
#include <Utf8.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "HyphenationCommon.h"
|
#include "HyphenationCommon.h"
|
||||||
@ -57,10 +60,13 @@ std::vector<Hyphenator::BreakInfo> Hyphenator::breakOffsets(const std::string& w
|
|||||||
|
|
||||||
// Convert to codepoints and normalize word boundaries.
|
// Convert to codepoints and normalize word boundaries.
|
||||||
auto cps = collectCodepoints(word);
|
auto cps = collectCodepoints(word);
|
||||||
trimSurroundingPunctuationAndFootnote(cps);
|
trimSurroundingPunctuation(cps);
|
||||||
|
trimTrailingFootnoteReference(cps);
|
||||||
const auto* hyphenator = cachedHyphenator_;
|
const auto* hyphenator = cachedHyphenator_;
|
||||||
|
const size_t minPrefix = hyphenator ? hyphenator->minPrefix() : LiangWordConfig::kDefaultMinPrefix;
|
||||||
|
const size_t minSuffix = hyphenator ? hyphenator->minSuffix() : LiangWordConfig::kDefaultMinSuffix;
|
||||||
|
|
||||||
// Explicit hyphen markers (soft or hard) take precedence over language breaks.
|
// Explicit hyphen markers (soft or hard) take precedence over heuristic breaks.
|
||||||
auto explicitBreakInfos = buildExplicitBreakInfos(cps);
|
auto explicitBreakInfos = buildExplicitBreakInfos(cps);
|
||||||
if (!explicitBreakInfos.empty()) {
|
if (!explicitBreakInfos.empty()) {
|
||||||
return explicitBreakInfos;
|
return explicitBreakInfos;
|
||||||
@ -74,8 +80,6 @@ std::vector<Hyphenator::BreakInfo> Hyphenator::breakOffsets(const std::string& w
|
|||||||
|
|
||||||
// Only add fallback breaks if needed
|
// Only add fallback breaks if needed
|
||||||
if (includeFallback && indexes.empty()) {
|
if (includeFallback && indexes.empty()) {
|
||||||
const size_t minPrefix = hyphenator ? hyphenator->minPrefix() : LiangWordConfig::kDefaultMinPrefix;
|
|
||||||
const size_t minSuffix = hyphenator ? hyphenator->minSuffix() : LiangWordConfig::kDefaultMinSuffix;
|
|
||||||
for (size_t idx = minPrefix; idx + minSuffix <= cps.size(); ++idx) {
|
for (size_t idx = minPrefix; idx + minSuffix <= cps.size(); ++idx) {
|
||||||
indexes.push_back(idx);
|
indexes.push_back(idx);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ constexpr int NUM_ITALIC_TAGS = sizeof(ITALIC_TAGS) / sizeof(ITALIC_TAGS[0]);
|
|||||||
const char* IMAGE_TAGS[] = {"img"};
|
const char* IMAGE_TAGS[] = {"img"};
|
||||||
constexpr int NUM_IMAGE_TAGS = sizeof(IMAGE_TAGS) / sizeof(IMAGE_TAGS[0]);
|
constexpr int NUM_IMAGE_TAGS = sizeof(IMAGE_TAGS) / sizeof(IMAGE_TAGS[0]);
|
||||||
|
|
||||||
const char* SKIP_TAGS[] = {"head"};
|
const char* SKIP_TAGS[] = {"head", "table"};
|
||||||
constexpr int NUM_SKIP_TAGS = sizeof(SKIP_TAGS) / sizeof(SKIP_TAGS[0]);
|
constexpr int NUM_SKIP_TAGS = sizeof(SKIP_TAGS) / sizeof(SKIP_TAGS[0]);
|
||||||
|
|
||||||
bool isWhitespace(const char c) { return c == ' ' || c == '\r' || c == '\n' || c == '\t'; }
|
bool isWhitespace(const char c) { return c == ' ' || c == '\r' || c == '\n' || c == '\t'; }
|
||||||
@ -63,43 +63,12 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special handling for tables - show placeholder text instead of dropping silently
|
|
||||||
if (strcmp(name, "table") == 0) {
|
|
||||||
// Add placeholder text
|
|
||||||
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
|
||||||
if (self->currentTextBlock) {
|
|
||||||
self->currentTextBlock->addWord("[Table omitted]", EpdFontFamily::ITALIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip table contents
|
|
||||||
self->skipUntilDepth = self->depth;
|
|
||||||
self->depth += 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS)) {
|
if (matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS)) {
|
||||||
// TODO: Start processing image tags
|
// TODO: Start processing image tags
|
||||||
std::string alt;
|
|
||||||
if (atts != nullptr) {
|
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
|
||||||
if (strcmp(atts[i], "alt") == 0) {
|
|
||||||
alt = "[Image: " + std::string(atts[i + 1]) + "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Serial.printf("[%lu] [EHP] Image alt: %s\n", millis(), alt.c_str());
|
|
||||||
|
|
||||||
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
|
||||||
self->italicUntilDepth = min(self->italicUntilDepth, self->depth);
|
|
||||||
self->depth += 1;
|
|
||||||
self->characterData(userData, alt.c_str(), alt.length());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Skip for now
|
|
||||||
self->skipUntilDepth = self->depth;
|
self->skipUntilDepth = self->depth;
|
||||||
self->depth += 1;
|
self->depth += 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (matches(name, SKIP_TAGS, NUM_SKIP_TAGS)) {
|
if (matches(name, SKIP_TAGS, NUM_SKIP_TAGS)) {
|
||||||
// start skip
|
// start skip
|
||||||
|
|||||||
@ -468,10 +468,7 @@ int GfxRenderer::getLineHeight(const int fontId) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char* btn2, const char* btn3,
|
void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char* btn2, const char* btn3,
|
||||||
const char* btn4) {
|
const char* btn4) const {
|
||||||
const Orientation orig_orientation = getOrientation();
|
|
||||||
setOrientation(Orientation::Portrait);
|
|
||||||
|
|
||||||
const int pageHeight = getScreenHeight();
|
const int pageHeight = getScreenHeight();
|
||||||
constexpr int buttonWidth = 106;
|
constexpr int buttonWidth = 106;
|
||||||
constexpr int buttonHeight = 40;
|
constexpr int buttonHeight = 40;
|
||||||
@ -484,15 +481,12 @@ void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char
|
|||||||
// Only draw if the label is non-empty
|
// Only draw if the label is non-empty
|
||||||
if (labels[i] != nullptr && labels[i][0] != '\0') {
|
if (labels[i] != nullptr && labels[i][0] != '\0') {
|
||||||
const int x = buttonPositions[i];
|
const int x = buttonPositions[i];
|
||||||
fillRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, false);
|
|
||||||
drawRect(x, pageHeight - buttonY, buttonWidth, buttonHeight);
|
drawRect(x, pageHeight - buttonY, buttonWidth, buttonHeight);
|
||||||
const int textWidth = getTextWidth(fontId, labels[i]);
|
const int textWidth = getTextWidth(fontId, labels[i]);
|
||||||
const int textX = x + (buttonWidth - 1 - textWidth) / 2;
|
const int textX = x + (buttonWidth - 1 - textWidth) / 2;
|
||||||
drawText(fontId, textX, pageHeight - buttonY + textYOffset, labels[i]);
|
drawText(fontId, textX, pageHeight - buttonY + textYOffset, labels[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setOrientation(orig_orientation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::drawSideButtonHints(const int fontId, const char* topBtn, const char* bottomBtn) const {
|
void GfxRenderer::drawSideButtonHints(const int fontId, const char* topBtn, const char* bottomBtn) const {
|
||||||
|
|||||||
@ -84,7 +84,7 @@ class GfxRenderer {
|
|||||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4);
|
void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4) const;
|
||||||
void drawSideButtonHints(int fontId, const char* topBtn, const char* bottomBtn) const;
|
void drawSideButtonHints(int fontId, const char* topBtn, const char* bottomBtn) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -45,9 +45,9 @@ lib_deps =
|
|||||||
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
|
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
|
||||||
EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay
|
EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay
|
||||||
SDCardManager=symlink://open-x4-sdk/libs/hardware/SDCardManager
|
SDCardManager=symlink://open-x4-sdk/libs/hardware/SDCardManager
|
||||||
bblanchon/ArduinoJson @ 7.4.2
|
ArduinoJson @ 7.4.2
|
||||||
ricmoo/QRCode @ 0.0.1
|
QRCode @ 0.0.1
|
||||||
links2004/WebSockets @ 2.7.3
|
links2004/WebSockets @ ^2.4.1
|
||||||
|
|
||||||
[env:default]
|
[env:default]
|
||||||
extends = base
|
extends = base
|
||||||
|
|||||||
@ -37,14 +37,6 @@ void WifiSelectionActivity::onEnter() {
|
|||||||
savePromptSelection = 0;
|
savePromptSelection = 0;
|
||||||
forgetPromptSelection = 0;
|
forgetPromptSelection = 0;
|
||||||
|
|
||||||
// Cache MAC address for display
|
|
||||||
uint8_t mac[6];
|
|
||||||
WiFi.macAddress(mac);
|
|
||||||
char macStr[32];
|
|
||||||
snprintf(macStr, sizeof(macStr), "MAC address: %02x-%02x-%02x-%02x-%02x-%02x", mac[0], mac[1], mac[2], mac[3], mac[4],
|
|
||||||
mac[5]);
|
|
||||||
cachedMacAddress = std::string(macStr);
|
|
||||||
|
|
||||||
// Trigger first update to show scanning message
|
// Trigger first update to show scanning message
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
@ -580,9 +572,6 @@ void WifiSelectionActivity::renderNetworkList() const {
|
|||||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 90, countStr);
|
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 90, countStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show MAC address above the network count and legend
|
|
||||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 105, cachedMacAddress.c_str());
|
|
||||||
|
|
||||||
// Draw help text
|
// Draw help text
|
||||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved");
|
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved");
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Connect", "", "");
|
const auto labels = mappedInput.mapLabels("« Back", "Connect", "", "");
|
||||||
|
|||||||
@ -62,9 +62,6 @@ class WifiSelectionActivity final : public ActivityWithSubactivity {
|
|||||||
// Password to potentially save (from keyboard or saved credentials)
|
// Password to potentially save (from keyboard or saved credentials)
|
||||||
std::string enteredPassword;
|
std::string enteredPassword;
|
||||||
|
|
||||||
// Cached MAC address string for display
|
|
||||||
std::string cachedMacAddress;
|
|
||||||
|
|
||||||
// Whether network was connected using a saved password (skip save prompt)
|
// Whether network was connected using a saved password (skip save prompt)
|
||||||
bool usedSavedPassword = false;
|
bool usedSavedPassword = false;
|
||||||
|
|
||||||
|
|||||||
@ -127,7 +127,7 @@ void XtcReaderActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool skipPages = SETTINGS.longPressChapterSkip && mappedInput.getHeldTime() > skipPageMs;
|
const bool skipPages = mappedInput.getHeldTime() > skipPageMs;
|
||||||
const int skipAmount = skipPages ? 10 : 1;
|
const int skipAmount = skipPages ? 10 : 1;
|
||||||
|
|
||||||
if (prevReleased) {
|
if (prevReleased) {
|
||||||
|
|||||||
@ -128,7 +128,8 @@ std::string positionsToHyphenated(const std::string& word, const std::vector<siz
|
|||||||
|
|
||||||
std::vector<size_t> hyphenateWordWithHyphenator(const std::string& word, const LanguageHyphenator& hyphenator) {
|
std::vector<size_t> hyphenateWordWithHyphenator(const std::string& word, const LanguageHyphenator& hyphenator) {
|
||||||
auto cps = collectCodepoints(word);
|
auto cps = collectCodepoints(word);
|
||||||
trimSurroundingPunctuationAndFootnote(cps);
|
trimSurroundingPunctuation(cps);
|
||||||
|
trimTrailingFootnoteReference(cps);
|
||||||
|
|
||||||
return hyphenator.breakIndexes(cps);
|
return hyphenator.breakIndexes(cps);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user