Skip to content

Commit

Permalink
In-page footnotes: allows for multiple id= inside them
Browse files Browse the repository at this point in the history
Previously, when entering a block element tagged with
"-cr-hint: footnote-inpage", we were associating it with
the first id= attribute found inside it.
It was then added to the page containing links with href=
this id.
But it may happen that some href= points to some inner id=
inside the footnote section (ie. the container has an id= but
the href= points to a <span id=> inside the container, with
another id), in which case no footnote was found for that href.
We now reference the same LVFootNote to all the ids met.
The LVFootNote stubs created when meeting the href need to
be kept, so we associate them with the "actual footnote" we
create, so it can act as a proxy to it.
  • Loading branch information
poire-z committed Feb 16, 2024
1 parent 95472fd commit dd5cf73
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 17 deletions.
77 changes: 72 additions & 5 deletions crengine/include/lvpagesplitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -326,21 +326,40 @@ typedef LVFastRef<LVFootNote> LVFootNoteRef;

class LVFootNote : public LVRefCounter {
lString32 id;
LVFootNote * actual_footnote; // when set, this LVFootNote is a proxy to this actual_footnote
CompactArray<LVRendLineInfo*, 2, 4> lines;
public:
LVFootNote( lString32 noteId )
: id(noteId)
: id(noteId), actual_footnote(NULL)
{
}
LVFootNote * getActualFootnote() {
return actual_footnote;
}
void setActualFootnote( LVFootNote * actualfootnote ) {
actual_footnote = actualfootnote;
}
void addLine( LVRendLineInfo * line )
{
lines.add( line );
}
// CompactArray<LVRendLineInfo*, 2, 4> & getLines() { printf("getLines %x\n", lines); return lines; }
LVRendLineInfo * getLine(int index) { return lines[index]; }
int length() { return lines.length(); }
bool empty() { return lines.empty(); }
void clear() { lines.clear(); }
LVRendLineInfo * getLine(int index) {
if ( actual_footnote )
return actual_footnote->lines[index];
return lines[index];
}
int length() {
if ( actual_footnote )
return actual_footnote->lines.length();
return lines.length();
}
bool empty() {
if ( actual_footnote )
return actual_footnote->lines.empty();
return lines.empty();
}
// void clear() { lines.clear(); }
lString32 getId() { return id; }
};

Expand Down Expand Up @@ -389,11 +408,58 @@ class LVRendPageContext
{
LVFootNoteRef ref = footNotes.get(id);
if ( ref.isNull() ) {
// Not found: create one and index it
ref = LVFootNoteRef( new LVFootNote( id ) );
footNotes.set( id, ref );
}
return ref;
}
LVFootNoteRef getOrCreateFootNote( lString32Collection & ids )
{
if (ids.length() == 1) {
return getOrCreateFootNote(ids.at(0));
}
// Multiple ids provided: zero, one, or more of them may exist
LVFootNoteRef ref;
int found = -1;
for ( int n=0; n<ids.length(); n++ ) {
ref = footNotes.get(ids.at(n));
if ( !ref.isNull() ) {
found = n;
break;
}
}
if ( found < 0 ) {
// None found: create one with (arbitrarily) the first of the ids provided
ref = LVFootNoteRef( new LVFootNote( ids.at(0) ) );
}
// Need to do something for each of the ids provided
for ( int n=0; n<ids.length(); n++ ) {
if (found < 0 ) {
// No existing footnote found: index with this id the same LVFootnote we created
footNotes.set( ids.at(n), ref );
}
else if ( n == found ) {
// The one we found (so, already indexed), and that we will return and
// that will get lines: nothing else to do.
}
else {
// Other ids may alredy exist or not
LVFootNoteRef nref = footNotes.get(ids.at(n));
if ( nref.isNull() ) {
// No existing footnote with this id: index with this id the same LVFootnote we created
footNotes.set( ids.at(n), ref );
}
else {
// Existing other footnote linking to one of our multiple ids:
// keep it, but make it be a proxy to the footnote we will return
// (and that will get lines)
nref.get()->setActualFootnote( ref.get() );
}
}
}
return ref;
}

void split();
public:
Expand Down Expand Up @@ -423,6 +489,7 @@ class LVRendPageContext

/// mark start of foot note
void enterFootNote( lString32 id );
void enterFootNote( lString32Collection & ids );

/// mark end of foot note
void leaveFootNote();
Expand Down
23 changes: 23 additions & 0 deletions crengine/src/lvpagesplitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ void LVRendPageContext::enterFootNote( lString32 id )
curr_note = getOrCreateFootNote( id ).get();
}

void LVRendPageContext::enterFootNote( lString32Collection & ids )
{
if ( !page_list )
return;
if ( curr_note != NULL ) {
CRLog::error("Nested entering note" );
return;
}
curr_note = getOrCreateFootNote( ids ).get();
}

/// mark end of foot note
void LVRendPageContext::leaveFootNote()
{
Expand Down Expand Up @@ -1068,6 +1079,13 @@ struct PageSplitState2 {
if ( cur_page_seen_footnotes.indexOf(note) >= 0 )
return;
cur_page_seen_footnotes.add(note);
// Also check the actual footnote if this one is just a proxy
LVFootNote * actual_footnote = note->getActualFootnote();
if ( actual_footnote ) {
if ( cur_page_seen_footnotes.indexOf(actual_footnote) >= 0 )
return;
cur_page_seen_footnotes.add(actual_footnote);
}

int note_nb_lines = note->length();
int note_top = -1;
Expand Down Expand Up @@ -1096,6 +1114,8 @@ struct PageSplitState2 {
// (even if one won't see the starting text and footnote number,
// it's better than seeing again the same duplicated footnote text)
cur_page_seen_footnotes.add(note);
if ( actual_footnote )
cur_page_seen_footnotes.add(actual_footnote);
}
// This footnote line fits
note_bottom = new_note_bottom;
Expand Down Expand Up @@ -1126,6 +1146,9 @@ struct PageSplitState2 {
continue;
if ( cur_page_seen_footnotes.indexOf(note) >= 0 )
continue; // Already shown on this page
LVFootNote * actual_footnote = note->getActualFootnote();
if ( actual_footnote && cur_page_seen_footnotes.indexOf(actual_footnote) >= 0 )
continue;
if ( !delayed_footnotes.empty() ) {
// Already some delayed footnotes
if ( delayed_footnotes.indexOf(note) < 0 )
Expand Down
25 changes: 14 additions & 11 deletions crengine/src/lvrend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4765,6 +4765,8 @@ int renderBlockElementLegacy( LVRendPageContext & context, ldomNode * enode, int
// to it, when -cr-hint: footnote-inpage is set on the footnote block container.
if ( STYLE_HAS_CR_HINT(style, FOOTNOTE_INPAGE) &&
enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES)) {
// Note: this has purposedly *not* been updated to use getAllInnerAttributeValues()
// like in renderBlockElementEnhanced, so we can compare their behaviours.
footnoteId = enode->getFirstInnerAttributeValue(attr_id);
if ( !footnoteId.empty() )
isFootNoteBody = true;
Expand Down Expand Up @@ -7220,20 +7222,21 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int

// See if this block is a footnote container, so we can deal with it accordingly
bool isFootNoteBody = false;
lString32 footnoteId;
lString32Collection footnoteIds;
// Allow displaying footnote content at the bottom of all pages that contain a link
// to it, when -cr-hint: footnote-inpage is set on the footnote block container.
if ( STYLE_HAS_CR_HINT(style, FOOTNOTE_INPAGE) &&
enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES)) {
footnoteId = enode->getFirstInnerAttributeValue(attr_id);
if ( !footnoteId.empty() )
enode->getAllInnerAttributeValues(attr_id, footnoteIds);
if ( footnoteIds.length() > 0 )
isFootNoteBody = true;
// Notes:
// It fails when that block element has itself an id, but links
// do target an other inline sub element id (getFirstInnerAttributeValue()
// would get the block element id, and there would be no existing footnote
// for the link target id).
// Not tested how it would behave with nested "-cr-hint: footnote-inpage"
// enterFootNote() takes care of not creating a new footnote if we are already
// inside a footnotebody (in case of nested "-cr-hint: footnote-inpage"), which
// should keep the state sane.
// If feels that if there are duplicated id= in the document, and they are
// involved in footnotes links and targets, things can get messy... No specific
// attention is currently given to this situation.
}
// For fb2 documents. Description of the <body> element from FictionBook2.2.xsd:
// Main content of the book, multiple bodies are used for additional
Expand Down Expand Up @@ -7998,7 +8001,7 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int
fmt.push();

if ( isFootNoteBody )
flow->getPageContext()->enterFootNote( footnoteId );
flow->getPageContext()->enterFootNote( footnoteIds );

// Ensure page-break-inside avoid, from the table's style or
// from outer containers
Expand Down Expand Up @@ -8170,7 +8173,7 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int
if (padding_top==0) {
flow->addContentLine(0, RN_SPLIT_AFTER_AVOID, 0, true);
}
flow->getPageContext()->enterFootNote( footnoteId );
flow->getPageContext()->enterFootNote( footnoteIds );
}

// recurse all sub-blocks for blocks
Expand Down Expand Up @@ -8647,7 +8650,7 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int
if (padding_top==0) {
flow->addContentLine(0, RN_SPLIT_AFTER_AVOID, 0, true);
}
flow->getPageContext()->enterFootNote( footnoteId );
flow->getPageContext()->enterFootNote( footnoteIds );
}

// We have lines of text in 'txform', that we should register
Expand Down
2 changes: 1 addition & 1 deletion crengine/src/lvtinydom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ extern const int gDOMVersionCurrent = DOM_VERSION_CURRENT;
// increment to force complete reload/reparsing of old file
#define CACHE_FILE_FORMAT_VERSION "3.05.72k"
/// increment following value to force re-formatting of old book after load
#define FORMATTING_VERSION_ID 0x0031
#define FORMATTING_VERSION_ID 0x0032

#ifndef DOC_DATA_COMPRESSION_LEVEL
/// data compression level (0=no compression, 1=fast compressions, 3=normal compression)
Expand Down

0 comments on commit dd5cf73

Please sign in to comment.