#include "hbclass.ch"

#define TEXT_MM_PER_POINT       0.352777777777778
#define TEXT_LINE_HEIGHT        1.0
#define TEXT_AVERAGE_GLYPH      0.52

CREATE CLASS TMotorPaginacion

   DATA hOpciones INIT { => }
   DATA lCreated INIT .F.

   METHOD New( hOpciones ) CONSTRUCTOR
   METHOD Init( hOpciones )
   METHOD Create()
   METHOD End()
   METHOD SetOpciones( hOpciones )
   METHOD Paginar( hPagina, aObjetos )
   METHOD DividirObjetos( aObjetos )

   HIDDEN:
   METHOD CalcularAltoContenido( aObjetos, nBottomMargin )
   METHOD DividirTexto( hObjeto )
   METHOD FragmentRichTextRuns( aSourceRuns, cFragment, hCursor )
   METHOD FragmentarLineas( hObjeto, aLineas )
   METHOD LineasPorPagina( hObjeto )
   METHOD LineasVisuales( hObjeto )
   METHOD CaracteresPorLinea( hObjeto )
   METHOD PartirParrafo( cTexto, nMaximo )
   METHOD SepararLineas( cTexto )
   METHOD UnirLineas( aLineas )
   METHOD HGet( hHash, cKey, uDefault )
   METHOD ValueNumber( uValue )

ENDCLASS

METHOD New( hOpciones ) CLASS TMotorPaginacion

   ::Init( hOpciones )

RETURN Self

METHOD Init( hOpciones ) CLASS TMotorPaginacion

   ::hOpciones := iif( HB_ISHASH( hOpciones ), hOpciones, { => } )
   ::lCreated := .F.

RETURN Self

METHOD Create() CLASS TMotorPaginacion

   ::lCreated := .T.

RETURN Self

METHOD End() CLASS TMotorPaginacion

   ::hOpciones := { => }
   ::lCreated := .F.

RETURN NIL

METHOD SetOpciones( hOpciones ) CLASS TMotorPaginacion

   ::hOpciones := iif( HB_ISHASH( hOpciones ), hOpciones, { => } )

RETURN Self

METHOD Paginar( hPagina, aObjetos ) CLASS TMotorPaginacion

   LOCAL oDocumento
   LOCAL aPaginas
   LOCAL nPagina
   LOCAL nAncho := ::ValueNumber( ::HGet( hPagina, "ancho", 0 ) )
   LOCAL nAlto := ::ValueNumber( ::HGet( hPagina, "alto", 0 ) )
   LOCAL cHeightMode := Lower( AllTrim( ::HGet( hPagina, "modoAlto", "fixed" ) ) )

   IF cHeightMode == "content"
      nAlto := ::CalcularAltoContenido( aObjetos, ::ValueNumber( ::HGet( hPagina, "margenInferiorContinuo", 5 ) ) )
   ELSE
      cHeightMode := "fixed"
   ENDIF
   oDocumento := TDocumentoRenderizable():New( nAncho, nAlto ):Create():SetPaperHeightMode( cHeightMode )
   IF cHeightMode == "content"
      oDocumento:AddPagina( iif( HB_ISARRAY( aObjetos ), aObjetos, {} ) )
      RETURN oDocumento
   ENDIF
   aPaginas := ::DividirObjetos( iif( HB_ISARRAY( aObjetos ), aObjetos, {} ) )
   FOR nPagina := 1 TO Len( aPaginas )
      oDocumento:AddPagina( aPaginas[ nPagina ] )
   NEXT

RETURN oDocumento

METHOD CalcularAltoContenido( aObjetos, nBottomMargin ) CLASS TMotorPaginacion

   LOCAL nBottom := 0.0
   LOCAL hObjeto
   LOCAL nTop
   LOCAL nHeight
   LOCAL nShadow

   IF ! HB_ISARRAY( aObjetos )
      RETURN Max( 1.0, nBottomMargin )
   ENDIF
   FOR EACH hObjeto IN aObjetos
      IF ! HB_ISHASH( hObjeto )
         LOOP
      ENDIF
      nTop := ::ValueNumber( ::HGet( hObjeto, "y", 0 ) )
      nHeight := Max( 0.0, ::ValueNumber( ::HGet( hObjeto, "alto", 0 ) ) )
      nShadow := Max( 0.0, ::ValueNumber( ::HGet( hObjeto, "sombraTamano", 0 ) ) )
      nBottom := Max( nBottom, nTop + nHeight + nShadow )
   NEXT

RETURN Max( 1.0, nBottom + Max( 0.0, nBottomMargin ) )

METHOD DividirObjetos( aObjetos ) CLASS TMotorPaginacion

   LOCAL aPaginas := { {} }
   LOCAL aFragmentos
   LOCAL nObjeto
   LOCAL nFragmento

   IF ! HB_ISARRAY( aObjetos )
      RETURN aPaginas
   ENDIF

   FOR nObjeto := 1 TO Len( aObjetos )
      IF ! HB_ISHASH( aObjetos[ nObjeto ] )
         LOOP
      ENDIF
      aFragmentos := ::DividirTexto( aObjetos[ nObjeto ] )
      FOR nFragmento := 1 TO Len( aFragmentos )
         DO WHILE Len( aPaginas ) < nFragmento
            AAdd( aPaginas, {} )
         ENDDO
         AAdd( aPaginas[ nFragmento ], aFragmentos[ nFragmento ] )
      NEXT
   NEXT

RETURN aPaginas

METHOD DividirTexto( hObjeto ) CLASS TMotorPaginacion

   LOCAL aLineas
   LOCAL aPaginasLineas
   LOCAL aFragmentos := {}
   LOCAL aLineasFragmento
   LOCAL hFragmento
   LOCAL cIdOrigen
   LOCAL nPagina
   LOCAL cType := ::HGet( hObjeto, "tipo", "" )
   LOCAL hRunCursor := { "nRun" => 1, "nChar" => 1 }
   LOCAL nPaginasLen
   LOCAL nLineasLen
   LOCAL aPaginaLineas
   LOCAL hLinea

   IF !( cType == "texto" .OR. cType == "texto_formateado" ) .OR. ::HGet( hObjeto, "permiteSaltoPagina", .F. ) != .T.
      RETURN { hObjeto }
   ENDIF

   aLineas := ::LineasVisuales( hObjeto )
   aPaginasLineas := ::FragmentarLineas( hObjeto, aLineas )
   IF Len( aPaginasLineas ) <= 1
      RETURN { hObjeto }
   ENDIF

   cIdOrigen := ::HGet( hObjeto, "id", "" )
   nPaginasLen := Len( aPaginasLineas )
   aFragmentos := Array( nPaginasLen )
   FOR EACH aPaginaLineas IN aPaginasLineas
      nPagina := aPaginaLineas:__enumIndex()
      nLineasLen := Len( aPaginaLineas )
      aLineasFragmento := Array( nLineasLen )
      FOR EACH hLinea IN aPaginaLineas
         aLineasFragmento[ hLinea:__enumIndex() ] := hLinea[ "texto" ]
      NEXT
      hFragmento := hb_HClone( hObjeto )
      hFragmento[ "texto" ] := ::UnirLineas( aLineasFragmento )
      IF cType == "texto_formateado"
         hFragmento[ "runs" ] := ::FragmentRichTextRuns( ::HGet( hObjeto, "runs", {} ), hFragmento[ "texto" ], hRunCursor )
      ENDIF
      hFragmento[ "finalesParrafo" ] := Array( nLineasLen )
      FOR EACH hLinea IN aPaginaLineas
         hFragmento[ "finalesParrafo" ][ hLinea:__enumIndex() ] := hLinea[ "finParrafo" ]
      NEXT
      hFragmento[ "idOrigenPaginacion" ] := cIdOrigen
      hFragmento[ "nPaginaContinuacion" ] := nPagina
      IF nPagina > 1 .AND. ! Empty( cIdOrigen )
         hFragmento[ "id" ] := cIdOrigen + "__page_" + hb_NToS( nPagina )
      ENDIF
      aFragmentos[ nPagina ] := hFragmento
   NEXT

RETURN aFragmentos

METHOD FragmentRichTextRuns( aSourceRuns, cFragment, hCursor ) CLASS TMotorPaginacion

   LOCAL aOutput := {}
   LOCAL hSource := { => }
   LOCAL hOutput
   LOCAL cSource := ""
   LOCAL cChar
   LOCAL cSourceChar
   LOCAL nIndex
   LOCAL nRun
   LOCAL nChar
   LOCAL nLast

   IF ! HB_ISARRAY( aSourceRuns ) .OR. ! HB_ISHASH( hCursor )
      RETURN aOutput
   ENDIF
   FOR nIndex := 1 TO Len( cFragment )
      nRun := ::HGet( hCursor, "nRun", 1 )
      nChar := ::HGet( hCursor, "nChar", 1 )
      DO WHILE nRun <= Len( aSourceRuns )
         hSource := aSourceRuns[ nRun ]
         cSource := iif( HB_ISHASH( hSource ), ::HGet( hSource, "cText", "" ), "" )
         IF nChar <= Len( cSource )
            EXIT
         ENDIF
         nRun++
         nChar := 1
      ENDDO
      IF nRun > Len( aSourceRuns )
         hSource := iif( Len( aSourceRuns ) > 0, ATail( aSourceRuns ), { => } )
         cSource := ""
      ENDIF

      cChar := SubStr( cFragment, nIndex, 1 )
      cSourceChar := SubStr( cSource, nChar, 1 )
      DO WHILE cChar != Chr( 10 ) .AND. cSourceChar == Chr( 10 )
         nChar++
         cSourceChar := SubStr( cSource, nChar, 1 )
      ENDDO

      nLast := Len( aOutput )
      IF nLast > 0 .AND. ::HGet( aOutput[ nLast ], "_run", 0 ) == nRun
         aOutput[ nLast ][ "cText" ] += cChar
      ELSE
         hOutput := iif( HB_ISHASH( hSource ), hb_HClone( hSource ), { => } )
         hOutput[ "cText" ] := cChar
         hOutput[ "_run" ] := nRun
         AAdd( aOutput, hOutput )
      ENDIF
      IF cChar != Chr( 10 ) .OR. cSourceChar == Chr( 10 )
         nChar++
      ENDIF
      hCursor[ "nRun" ] := nRun
      hCursor[ "nChar" ] := nChar
   NEXT
   FOR EACH hOutput IN aOutput
      hb_HDel( hOutput, "_run" )
   NEXT

RETURN aOutput

METHOD FragmentarLineas( hObjeto, aLineas ) CLASS TMotorPaginacion

   LOCAL nAlto := Max( 0, ::ValueNumber( ::HGet( hObjeto, "alto", 0 ) ) ) - ;
      Max( 0, ::ValueNumber( ::HGet( hObjeto, "paddingSuperior", 0 ) ) ) - ;
      Max( 0, ::ValueNumber( ::HGet( hObjeto, "paddingInferior", 0 ) ) )
   LOCAL nTamano := Max( 0.1, ::ValueNumber( ::HGet( hObjeto, "tamano", 10 ) ) )
   LOCAL nInterlineado := ::ValueNumber( ::HGet( hObjeto, "interlineado", 0 ) )
   LOCAL nEspaciadoParrafo := ::ValueNumber( ::HGet( hObjeto, "espaciadoParrafo", nInterlineado ) )
   LOCAL aPaginas := {}
   LOCAL aActual := {}
   LOCAL nUsado := 0
   LOCAL nEspaciado
   LOCAL nCoste
   LOCAL hLinea

   IF nAlto <= 0 .OR. Len( aLineas ) == 0
      RETURN { aLineas }
   ENDIF

   FOR EACH hLinea IN aLineas
      nEspaciado := iif( ::HGet( hLinea, "finParrafo", .F. ), nEspaciadoParrafo, nInterlineado )
      nCoste := Max( 0.1, ( ( nTamano * TEXT_LINE_HEIGHT ) + nEspaciado ) * TEXT_MM_PER_POINT )
      IF Len( aActual ) > 0 .AND. nUsado + nCoste > nAlto + 0.000001
         AAdd( aPaginas, aActual )
         aActual := {}
         nUsado := 0
      ENDIF
      AAdd( aActual, hLinea )
      nUsado += nCoste
   NEXT
   IF Len( aActual ) > 0
      AAdd( aPaginas, aActual )
   ENDIF

RETURN iif( Len( aPaginas ) == 0, { {} }, aPaginas )

METHOD LineasPorPagina( hObjeto ) CLASS TMotorPaginacion

   LOCAL nAlto := Max( 0, ::ValueNumber( ::HGet( hObjeto, "alto", 0 ) ) ) - ;
      Max( 0, ::ValueNumber( ::HGet( hObjeto, "paddingSuperior", 0 ) ) ) - ;
      Max( 0, ::ValueNumber( ::HGet( hObjeto, "paddingInferior", 0 ) ) )
   LOCAL nTamano := Max( 0.1, ::ValueNumber( ::HGet( hObjeto, "tamano", 10 ) ) )
   LOCAL nInterlineado := ::ValueNumber( ::HGet( hObjeto, "interlineado", 0 ) )
   LOCAL nAltoLinea := Max( 0.1, ( ( nTamano * TEXT_LINE_HEIGHT ) + nInterlineado ) * TEXT_MM_PER_POINT )

RETURN iif( nAlto > 0, Max( 1, Int( nAlto / nAltoLinea ) ), 0 )

METHOD LineasVisuales( hObjeto ) CLASS TMotorPaginacion

   LOCAL cTexto := ::HGet( hObjeto, "texto", "" )
   LOCAL aParrafos
   LOCAL aLineas := {}
   LOCAL aPartes
   LOCAL nCaracteres
   LOCAL nParrafo
   LOCAL nParte

   cTexto := StrTran( cTexto, Chr( 13 ) + Chr( 10 ), Chr( 10 ) )
   cTexto := StrTran( cTexto, Chr( 13 ), Chr( 10 ) )
   aParrafos := ::SepararLineas( cTexto )
   IF ::HGet( hObjeto, "ajusteLinea", .T. ) == .F.
      FOR nParrafo := 1 TO Len( aParrafos )
         AAdd( aLineas, { "texto" => aParrafos[ nParrafo ], "finParrafo" => .T. } )
      NEXT
      RETURN aLineas
   ENDIF

   nCaracteres := ::CaracteresPorLinea( hObjeto )
   FOR nParrafo := 1 TO Len( aParrafos )
      aPartes := ::PartirParrafo( aParrafos[ nParrafo ], nCaracteres )
      FOR nParte := 1 TO Len( aPartes )
         AAdd( aLineas, { ;
            "texto" => aPartes[ nParte ], ;
            "finParrafo" => nParte == Len( aPartes ) } )
      NEXT
   NEXT

RETURN aLineas

METHOD CaracteresPorLinea( hObjeto ) CLASS TMotorPaginacion

   LOCAL nAncho := Max( 0, ::ValueNumber( ::HGet( hObjeto, "ancho", 0 ) ) ) - ;
      Max( 0, ::ValueNumber( ::HGet( hObjeto, "paddingIzquierdo", 0 ) ) ) - ;
      Max( 0, ::ValueNumber( ::HGet( hObjeto, "paddingDerecho", 0 ) ) )
   LOCAL nTamano := Max( 0.1, ::ValueNumber( ::HGet( hObjeto, "tamano", 10 ) ) )
   LOCAL nAnchoCaracter := nTamano * TEXT_MM_PER_POINT * TEXT_AVERAGE_GLYPH

RETURN iif( nAncho > 0, Max( 1, Int( nAncho / nAnchoCaracter ) ), 2147483647 )

METHOD PartirParrafo( cTexto, nMaximo ) CLASS TMotorPaginacion

   LOCAL aLineas := {}
   LOCAL nLongitud := hb_UTF8Len( cTexto )
   LOCAL nInicio := 1
   LOCAL nRestante
   LOCAL nCorte
   LOCAL nIndice
   LOCAL cCaracter

   IF nLongitud == 0
      RETURN { "" }
   ENDIF

   DO WHILE nInicio <= nLongitud
      nRestante := nLongitud - nInicio + 1
      IF nRestante <= nMaximo
         AAdd( aLineas, hb_UTF8SubStr( cTexto, nInicio ) )
         EXIT
      ENDIF
      nCorte := nMaximo
      FOR nIndice := nMaximo TO 1 STEP -1
         cCaracter := hb_UTF8SubStr( cTexto, nInicio + nIndice - 1, 1 )
         IF cCaracter == " " .OR. cCaracter == Chr( 9 )
            nCorte := nIndice
            EXIT
         ENDIF
      NEXT
      AAdd( aLineas, hb_UTF8SubStr( cTexto, nInicio, nCorte ) )
      nInicio += nCorte
   ENDDO

RETURN aLineas

METHOD SepararLineas( cTexto ) CLASS TMotorPaginacion

   LOCAL aLineas := {}
   LOCAL nInicio := 1
   LOCAL nSalto

   DO WHILE .T.
      nSalto := At( Chr( 10 ), SubStr( cTexto, nInicio ) )
      IF nSalto == 0
         AAdd( aLineas, SubStr( cTexto, nInicio ) )
         EXIT
      ENDIF
      AAdd( aLineas, SubStr( cTexto, nInicio, nSalto - 1 ) )
      nInicio += nSalto
   ENDDO

RETURN aLineas

METHOD UnirLineas( aLineas ) CLASS TMotorPaginacion

   LOCAL cTexto := ""
   LOCAL nLinea

   FOR nLinea := 1 TO Len( aLineas )
      IF nLinea > 1
         cTexto += Chr( 10 )
      ENDIF
      cTexto += aLineas[ nLinea ]
   NEXT

RETURN cTexto

METHOD HGet( hHash, cKey, uDefault ) CLASS TMotorPaginacion

   IF HB_ISHASH( hHash ) .AND. hb_HHasKey( hHash, cKey )
      RETURN hHash[ cKey ]
   ENDIF

RETURN uDefault

METHOD ValueNumber( uValue ) CLASS TMotorPaginacion

   IF HB_ISNUMERIC( uValue )
      RETURN uValue
   ENDIF
   IF HB_ISSTRING( uValue )
      RETURN Val( StrTran( uValue, ",", "." ) )
   ENDIF
   IF HB_ISLOGICAL( uValue )
      RETURN iif( uValue, 1, 0 )
   ENDIF

RETURN 0
