@@ -1542,6 +1542,105 @@ class Catalog {
15421542 * exist in most PDF documents).
15431543 */
15441544
1545+ /**
1546+ * Derive a destination array from a Structure Element reference.
1547+ * Walks the SE dict to find its page (Pg) and optional bounding box (A.BBox),
1548+ * then returns an XYZ destination array that can be used for navigation.
1549+ * @param {XRef } xref
1550+ * @param {Ref } seRef
1551+ * @returns {Array|null }
1552+ */
1553+ static #getDestFromStructElement( xref , seRef ) {
1554+ const seDict = xref . fetchIfRef ( seRef ) ;
1555+ if ( ! ( seDict instanceof Dict ) ) {
1556+ return null ;
1557+ }
1558+
1559+ // Try to find the page reference for this structure element.
1560+ // Search order: the element itself, its descendants down to leaf nodes,
1561+ // then ancestor elements via the P entry (up).
1562+ let pageRef = null ;
1563+
1564+ // Check the element directly.
1565+ const directPg = seDict . getRaw ( "Pg" ) ;
1566+ if ( directPg instanceof Ref ) {
1567+ pageRef = directPg ;
1568+ }
1569+
1570+ // Walk down into descendants (BFS) until a Pg is found or leaves are
1571+ // reached (e.g. integer MCIDs or MCR/OBJR dicts without further K).
1572+ if ( ! pageRef ) {
1573+ const queue = [ seDict ] ;
1574+ while ( queue . length > 0 && ! pageRef ) {
1575+ const node = queue . shift ( ) ;
1576+ const kids = node . get ( "K" ) ;
1577+ let kidsArr ;
1578+ if ( Array . isArray ( kids ) ) {
1579+ kidsArr = kids ;
1580+ } else if ( kids ) {
1581+ kidsArr = [ kids ] ;
1582+ } else {
1583+ kidsArr = [ ] ;
1584+ }
1585+ for ( const kid of kidsArr ) {
1586+ const kidObj = xref . fetchIfRef ( kid ) ;
1587+ if ( ! ( kidObj instanceof Dict ) ) {
1588+ continue ; // integer MCID – leaf node, no Pg here
1589+ }
1590+ const pg = kidObj . getRaw ( "Pg" ) ;
1591+ if ( pg instanceof Ref ) {
1592+ pageRef = pg ;
1593+ break ;
1594+ }
1595+ queue . push ( kidObj ) ;
1596+ }
1597+ }
1598+ }
1599+
1600+ // Walk up the parent chain if still not found.
1601+ if ( ! pageRef ) {
1602+ const MAX_DEPTH = 40 ;
1603+ let current = seDict ;
1604+ for ( let depth = 0 ; depth < MAX_DEPTH ; depth ++ ) {
1605+ const parentRaw = current . getRaw ( "P" ) ;
1606+ if ( ! ( parentRaw instanceof Ref ) ) {
1607+ break ;
1608+ }
1609+ const parentDict = xref . fetchIfRef ( parentRaw ) ;
1610+ if ( ! ( parentDict instanceof Dict ) ) {
1611+ break ;
1612+ }
1613+ if ( isName ( parentDict . get ( "Type" ) , "StructTreeRoot" ) ) {
1614+ break ;
1615+ }
1616+ const pg = parentDict . getRaw ( "Pg" ) ;
1617+ if ( pg instanceof Ref ) {
1618+ pageRef = pg ;
1619+ break ;
1620+ }
1621+ current = parentDict ;
1622+ }
1623+ }
1624+
1625+ if ( ! pageRef ) {
1626+ return null ;
1627+ }
1628+
1629+ // Try to obtain precise coordinates from the element's attribute BBox.
1630+ let x = null ,
1631+ y = null ;
1632+ const attrs = seDict . get ( "A" ) ;
1633+ if ( attrs instanceof Dict ) {
1634+ const bboxArr = attrs . getArray ( "BBox" ) ;
1635+ if ( isNumberArray ( bboxArr , 4 ) ) {
1636+ x = bboxArr [ 0 ] ;
1637+ y = bboxArr [ 3 ] ; // top of the bbox in PDF page coordinates
1638+ }
1639+ }
1640+
1641+ return [ pageRef , { name : "XYZ" } , x , y , null ] ;
1642+ }
1643+
15451644 /**
15461645 * Helper function used to parse the contents of destination dictionaries.
15471646 * @param {ParseDestDictionaryParameters } params
@@ -1773,6 +1872,35 @@ class Catalog {
17731872 resultObj . dest = dest ;
17741873 }
17751874 }
1875+
1876+ // Handle SE (Structure Element) entry: when no other destination has been
1877+ // found, derive one from the structure element's page and optional bbox.
1878+ if (
1879+ ! resultObj . dest &&
1880+ ! resultObj . url &&
1881+ ! resultObj . action &&
1882+ ! resultObj . attachment &&
1883+ ! resultObj . setOCGState &&
1884+ ! resultObj . resetForm
1885+ ) {
1886+ const seRef = destDict . getRaw ( "SE" ) ;
1887+ if ( seRef instanceof Ref ) {
1888+ try {
1889+ const seDest = Catalog . #getDestFromStructElement(
1890+ destDict . xref ,
1891+ seRef
1892+ ) ;
1893+ if ( seDest ) {
1894+ resultObj . dest = seDest ;
1895+ }
1896+ } catch ( ex ) {
1897+ if ( ex instanceof MissingDataException ) {
1898+ throw ex ;
1899+ }
1900+ info ( "SE parsing failed." ) ;
1901+ }
1902+ }
1903+ }
17761904 }
17771905}
17781906
0 commit comments