Skip to content

Commit ac13847

Browse files
authored
fix: separate superscript citation/affiliation callouts from adjacent words (#251)
A superscript run (citation callout '13-15', affiliation marker '1,2,*') that abuts a baseline word with no space character in the content stream merges into it in the ALTO ('developed13-15', 'Kowalczyk1,2,*'). GROBID's tokenizer splits on whitespace and punctuation but not letter<->digit, so the callout is hidden. Emit an inter-word <SP> when the font pass from normal to superscript. Subscripts (lowered, e.g. 'CO2') are left joined so chemical formulae stay intact.
1 parent 5ffebe4 commit ac13847

1 file changed

Lines changed: 26 additions & 1 deletion

File tree

src/XmlAltoOutputDev.cc

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6625,7 +6625,32 @@ void TextPage::dump(GBool noLineNumbers, GBool fullFontName, const vector<bool>
66256625
xmlAddChild(nodeline, node);
66266626
nonEmptyLine = true;
66276627

6628-
if (wordI < line1->words->getLength() - 1 and (word->spaceAfter == gTrue)) {
6628+
// Force a token boundary before/after a SUPERSCRIPT run. A superscript
6629+
// citation or affiliation callout ("13-15", "1,2,*") abutting a baseline
6630+
// word with no space character in the content stream otherwise merges
6631+
// into it ("developed13-15", "Kowalczyk1,2,*"); strict letter/digit
6632+
// tokenizers (e.g. GROBID's) will not split that, hiding the callout.
6633+
// The smaller-font neighbour is the scripted run; it is a SUPERscript
6634+
// when its baseline is raised relative to the baseline word. Subscripts
6635+
// (lowered — e.g. the "2" in a chemical formula "CO2") are deliberately
6636+
// NOT separated, so formulae stay intact. Adjacent words are tested
6637+
// directly to avoid depending on the running-line-baseline state.
6638+
bool scriptBoundary = false;
6639+
if (nextWord != NULL && word->fontSize > 0 && nextWord->fontSize > 0 &&
6640+
word->fontSize != nextWord->fontSize) {
6641+
bool wordSmaller = word->fontSize < nextWord->fontSize;
6642+
double larger = wordSmaller ? nextWord->fontSize : word->fontSize;
6643+
double smaller = wordSmaller ? word->fontSize : nextWord->fontSize;
6644+
double scriptBase = wordSmaller ? word->base : nextWord->base;
6645+
double baselineBase = wordSmaller ? nextWord->base : word->base;
6646+
// markedly smaller font, and the scripted run's baseline raised
6647+
// (base value smaller => higher on the page) => superscript.
6648+
if (smaller <= 0.85 * larger &&
6649+
(baselineBase - scriptBase) >= 0.15 * larger)
6650+
scriptBoundary = true;
6651+
}
6652+
6653+
if (wordI < line1->words->getLength() - 1 and (word->spaceAfter == gTrue or scriptBoundary)) {
66296654
xmlNodePtr spacingNode = xmlNewNode(NULL, (const xmlChar *) TAG_SPACING);
66306655
spacingNode->type = XML_ELEMENT_NODE;
66316656
// The inter-word gap can be negative when consecutive words overlap

0 commit comments

Comments
 (0)