github.com/neohugo/neohugo@v0.123.8/hugolib/page.go (about)

     1  // Copyright 2024 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hugolib
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"strconv"
    20  	"sync"
    21  	"sync/atomic"
    22  
    23  	"github.com/neohugo/neohugo/hugofs"
    24  	"github.com/neohugo/neohugo/hugolib/doctree"
    25  	"github.com/neohugo/neohugo/identity"
    26  	"github.com/neohugo/neohugo/media"
    27  	"github.com/neohugo/neohugo/output"
    28  	"github.com/neohugo/neohugo/output/layouts"
    29  	"github.com/neohugo/neohugo/related"
    30  	"github.com/spf13/afero"
    31  
    32  	"github.com/neohugo/neohugo/markup/converter"
    33  	"github.com/neohugo/neohugo/markup/tableofcontents"
    34  
    35  	"github.com/neohugo/neohugo/tpl"
    36  
    37  	"github.com/neohugo/neohugo/common/herrors"
    38  	"github.com/neohugo/neohugo/common/maps"
    39  
    40  	"github.com/neohugo/neohugo/source"
    41  
    42  	"github.com/neohugo/neohugo/common/collections"
    43  	"github.com/neohugo/neohugo/common/text"
    44  	"github.com/neohugo/neohugo/resources/kinds"
    45  	"github.com/neohugo/neohugo/resources/page"
    46  	"github.com/neohugo/neohugo/resources/resource"
    47  )
    48  
    49  var (
    50  	_ page.Page                                = (*pageState)(nil)
    51  	_ collections.Grouper                      = (*pageState)(nil)
    52  	_ collections.Slicer                       = (*pageState)(nil)
    53  	_ identity.DependencyManagerScopedProvider = (*pageState)(nil)
    54  	_ contentNodeI                             = (*pageState)(nil)
    55  	_ pageContext                              = (*pageState)(nil)
    56  )
    57  
    58  var (
    59  	pageTypesProvider = resource.NewResourceTypesProvider(media.Builtin.OctetType, pageResourceType)
    60  	nopPageOutput     = &pageOutput{
    61  		pagePerOutputProviders: nopPagePerOutput,
    62  		ContentProvider:        page.NopPage,
    63  	}
    64  )
    65  
    66  // pageContext provides contextual information about this page, for error
    67  // logging and similar.
    68  type pageContext interface {
    69  	posOffset(offset int) text.Position
    70  	wrapError(err error) error
    71  	getContentConverter() converter.Converter
    72  }
    73  
    74  type pageSiteAdapter struct {
    75  	p page.Page
    76  	s *Site
    77  }
    78  
    79  func (pa pageSiteAdapter) GetPage(ref string) (page.Page, error) {
    80  	p, err := pa.s.getPage(pa.p, ref)
    81  
    82  	if p == nil {
    83  		// The nil struct has meaning in some situations, mostly to avoid breaking
    84  		// existing sites doing $nilpage.IsDescendant($p), which will always return
    85  		// false.
    86  		p = page.NilPage
    87  	}
    88  	return p, err
    89  }
    90  
    91  type pageState struct {
    92  	// Incremented for each new page created.
    93  	// Note that this will change between builds for a given Page.
    94  	pid uint64
    95  
    96  	// This slice will be of same length as the number of global slice of output
    97  	// formats (for all sites).
    98  	pageOutputs []*pageOutput
    99  
   100  	// Used to determine if we can reuse content across output formats.
   101  	pageOutputTemplateVariationsState *atomic.Uint32
   102  
   103  	// This will be shifted out when we start to render a new output format.
   104  	pageOutputIdx int
   105  	*pageOutput
   106  
   107  	// Common for all output formats.
   108  	*pageCommon
   109  
   110  	resource.Staler
   111  	dependencyManager    identity.Manager
   112  	resourcesPublishInit *sync.Once
   113  }
   114  
   115  func (p *pageState) IdentifierBase() string {
   116  	return p.Path()
   117  }
   118  
   119  func (p *pageState) GetIdentity() identity.Identity {
   120  	return p
   121  }
   122  
   123  func (p *pageState) ForEeachIdentity(f func(identity.Identity) bool) bool {
   124  	return f(p)
   125  }
   126  
   127  func (p *pageState) GetDependencyManager() identity.Manager {
   128  	return p.dependencyManager
   129  }
   130  
   131  func (p *pageState) GetDependencyManagerForScope(scope int) identity.Manager {
   132  	switch scope {
   133  	case pageDependencyScopeDefault:
   134  		return p.dependencyManagerOutput
   135  	case pageDependencyScopeGlobal:
   136  		return p.dependencyManager
   137  	default:
   138  		return identity.NopManager
   139  	}
   140  }
   141  
   142  func (p *pageState) Key() string {
   143  	return "page-" + strconv.FormatUint(p.pid, 10)
   144  }
   145  
   146  func (p *pageState) resetBuildState() {
   147  	p.Scratcher = maps.NewScratcher()
   148  }
   149  
   150  func (p *pageState) reusePageOutputContent() bool {
   151  	return p.pageOutputTemplateVariationsState.Load() == 1
   152  }
   153  
   154  func (po *pageState) isRenderedAny() bool {
   155  	for _, o := range po.pageOutputs {
   156  		if o.isRendered() {
   157  			return true
   158  		}
   159  	}
   160  	return false
   161  }
   162  
   163  func (p *pageState) isContentNodeBranch() bool {
   164  	return p.IsNode()
   165  }
   166  
   167  func (p *pageState) Err() resource.ResourceError {
   168  	return nil
   169  }
   170  
   171  // Eq returns whether the current page equals the given page.
   172  // This is what's invoked when doing `{{ if eq $page $otherPage }}`
   173  func (p *pageState) Eq(other any) bool {
   174  	pp, err := unwrapPage(other)
   175  	if err != nil {
   176  		return false
   177  	}
   178  
   179  	return p == pp
   180  }
   181  
   182  func (p *pageState) HeadingsFiltered(context.Context) tableofcontents.Headings {
   183  	return nil
   184  }
   185  
   186  type pageHeadingsFiltered struct {
   187  	*pageState
   188  	headings tableofcontents.Headings
   189  }
   190  
   191  func (p *pageHeadingsFiltered) HeadingsFiltered(context.Context) tableofcontents.Headings {
   192  	return p.headings
   193  }
   194  
   195  func (p *pageHeadingsFiltered) page() page.Page {
   196  	return p.pageState
   197  }
   198  
   199  // For internal use by the related content feature.
   200  func (p *pageState) ApplyFilterToHeadings(ctx context.Context, fn func(*tableofcontents.Heading) bool) related.Document {
   201  	r, err := p.m.content.contentToC(ctx, p.pageOutput.pco)
   202  	if err != nil {
   203  		panic(err)
   204  	}
   205  	headings := r.tableOfContents.Headings.FilterBy(fn)
   206  	return &pageHeadingsFiltered{
   207  		pageState: p,
   208  		headings:  headings,
   209  	}
   210  }
   211  
   212  func (p *pageState) GitInfo() source.GitInfo {
   213  	return p.gitInfo
   214  }
   215  
   216  func (p *pageState) CodeOwners() []string {
   217  	return p.codeowners
   218  }
   219  
   220  // GetTerms gets the terms defined on this page in the given taxonomy.
   221  // The pages returned will be ordered according to the front matter.
   222  func (p *pageState) GetTerms(taxonomy string) page.Pages {
   223  	return p.s.pageMap.getTermsForPageInTaxonomy(p.Path(), taxonomy)
   224  }
   225  
   226  func (p *pageState) MarshalJSON() ([]byte, error) {
   227  	return page.MarshalPageToJSON(p)
   228  }
   229  
   230  func (p *pageState) RegularPagesRecursive() page.Pages {
   231  	switch p.Kind() {
   232  	case kinds.KindSection, kinds.KindHome:
   233  		return p.s.pageMap.getPagesInSection(
   234  			pageMapQueryPagesInSection{
   235  				pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
   236  					Path:    p.Path(),
   237  					Include: pagePredicates.ShouldListLocal.And(pagePredicates.KindPage),
   238  				},
   239  				Recursive: true,
   240  			},
   241  		)
   242  	default:
   243  		return p.RegularPages()
   244  	}
   245  }
   246  
   247  func (p *pageState) PagesRecursive() page.Pages {
   248  	return nil
   249  }
   250  
   251  func (p *pageState) RegularPages() page.Pages {
   252  	switch p.Kind() {
   253  	case kinds.KindPage:
   254  	case kinds.KindSection, kinds.KindHome, kinds.KindTaxonomy:
   255  		return p.s.pageMap.getPagesInSection(
   256  			pageMapQueryPagesInSection{
   257  				pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
   258  					Path:    p.Path(),
   259  					Include: pagePredicates.ShouldListLocal.And(pagePredicates.KindPage),
   260  				},
   261  			},
   262  		)
   263  	case kinds.KindTerm:
   264  		return p.s.pageMap.getPagesWithTerm(
   265  			pageMapQueryPagesBelowPath{
   266  				Path:    p.Path(),
   267  				Include: pagePredicates.ShouldListLocal.And(pagePredicates.KindPage),
   268  			},
   269  		)
   270  	default:
   271  		return p.s.RegularPages()
   272  	}
   273  	return nil
   274  }
   275  
   276  func (p *pageState) Pages() page.Pages {
   277  	switch p.Kind() {
   278  	case kinds.KindPage:
   279  	case kinds.KindSection, kinds.KindHome:
   280  		return p.s.pageMap.getPagesInSection(
   281  			pageMapQueryPagesInSection{
   282  				pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
   283  					Path:    p.Path(),
   284  					KeyPart: "page-section",
   285  					Include: pagePredicates.ShouldListLocal.And(
   286  						pagePredicates.KindPage.Or(pagePredicates.KindSection),
   287  					),
   288  				},
   289  			},
   290  		)
   291  	case kinds.KindTerm:
   292  		return p.s.pageMap.getPagesWithTerm(
   293  			pageMapQueryPagesBelowPath{
   294  				Path: p.Path(),
   295  			},
   296  		)
   297  	case kinds.KindTaxonomy:
   298  		return p.s.pageMap.getPagesInSection(
   299  			pageMapQueryPagesInSection{
   300  				pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
   301  					Path:    p.Path(),
   302  					KeyPart: "term",
   303  					Include: pagePredicates.ShouldListLocal.And(pagePredicates.KindTerm),
   304  				},
   305  				Recursive: true,
   306  			},
   307  		)
   308  	default:
   309  		return p.s.Pages()
   310  	}
   311  	return nil
   312  }
   313  
   314  // RawContent returns the un-rendered source content without
   315  // any leading front matter.
   316  func (p *pageState) RawContent() string {
   317  	if p.m.content.pi.itemsStep2 == nil {
   318  		return ""
   319  	}
   320  	start := p.m.content.pi.posMainContent
   321  	if start == -1 {
   322  		start = 0
   323  	}
   324  	source, err := p.m.content.pi.contentSource(p.m.content)
   325  	if err != nil {
   326  		panic(err)
   327  	}
   328  	return string(source[start:])
   329  }
   330  
   331  func (p *pageState) Resources() resource.Resources {
   332  	return p.s.pageMap.getOrCreateResourcesForPage(p)
   333  }
   334  
   335  func (p *pageState) HasShortcode(name string) bool {
   336  	if p.m.content.shortcodeState == nil {
   337  		return false
   338  	}
   339  
   340  	return p.m.content.shortcodeState.hasName(name)
   341  }
   342  
   343  func (p *pageState) Site() page.Site {
   344  	return p.sWrapped
   345  }
   346  
   347  func (p *pageState) String() string {
   348  	return fmt.Sprintf("Page(%s)", p.Path())
   349  }
   350  
   351  // IsTranslated returns whether this content file is translated to
   352  // other language(s).
   353  func (p *pageState) IsTranslated() bool {
   354  	return len(p.Translations()) > 0
   355  }
   356  
   357  // TranslationKey returns the key used to identify a translation of this content.
   358  func (p *pageState) TranslationKey() string {
   359  	if p.m.pageConfig.TranslationKey != "" {
   360  		return p.m.pageConfig.TranslationKey
   361  	}
   362  	return p.Path()
   363  }
   364  
   365  // AllTranslations returns all translations, including the current Page.
   366  func (p *pageState) AllTranslations() page.Pages {
   367  	key := p.Path() + "/" + "translations-all"
   368  	pages, err := p.s.pageMap.getOrCreatePagesFromCache(key, func(string) (page.Pages, error) {
   369  		if p.m.pageConfig.TranslationKey != "" {
   370  			// translationKey set by user.
   371  			pas, _ := p.s.h.translationKeyPages.Get(p.m.pageConfig.TranslationKey)
   372  			pasc := make(page.Pages, len(pas))
   373  			copy(pasc, pas)
   374  			page.SortByLanguage(pasc)
   375  			return pasc, nil
   376  		}
   377  		var pas page.Pages
   378  		p.s.pageMap.treePages.ForEeachInDimension(p.Path(), doctree.DimensionLanguage.Index(),
   379  			func(n contentNodeI) bool {
   380  				if n != nil {
   381  					pas = append(pas, n.(page.Page))
   382  				}
   383  				return false
   384  			},
   385  		)
   386  
   387  		pas = pagePredicates.ShouldLink.Filter(pas)
   388  		page.SortByLanguage(pas)
   389  		return pas, nil
   390  	})
   391  	if err != nil {
   392  		panic(err)
   393  	}
   394  
   395  	return pages
   396  }
   397  
   398  // Translations returns the translations excluding the current Page.
   399  func (p *pageState) Translations() page.Pages {
   400  	key := p.Path() + "/" + "translations"
   401  	pages, err := p.s.pageMap.getOrCreatePagesFromCache(key, func(string) (page.Pages, error) {
   402  		var pas page.Pages
   403  		for _, pp := range p.AllTranslations() {
   404  			if !pp.Eq(p) {
   405  				pas = append(pas, pp)
   406  			}
   407  		}
   408  		return pas, nil
   409  	})
   410  	if err != nil {
   411  		panic(err)
   412  	}
   413  	return pages
   414  }
   415  
   416  func (ps *pageState) initCommonProviders(pp pagePaths) error {
   417  	if ps.IsPage() {
   418  		ps.posNextPrev = &nextPrev{init: ps.s.init.prevNext}
   419  		ps.posNextPrevSection = &nextPrev{init: ps.s.init.prevNextInSection}
   420  		ps.InSectionPositioner = newPagePositionInSection(ps.posNextPrevSection)
   421  		ps.Positioner = newPagePosition(ps.posNextPrev)
   422  	}
   423  
   424  	ps.OutputFormatsProvider = pp
   425  	ps.targetPathDescriptor = pp.targetPathDescriptor
   426  	ps.RefProvider = newPageRef(ps)
   427  	ps.SitesProvider = ps.s
   428  
   429  	return nil
   430  }
   431  
   432  func (p *pageState) getLayoutDescriptor() layouts.LayoutDescriptor {
   433  	p.layoutDescriptorInit.Do(func() {
   434  		var section string
   435  		sections := p.SectionsEntries()
   436  
   437  		switch p.Kind() {
   438  		case kinds.KindSection:
   439  			if len(sections) > 0 {
   440  				section = sections[0]
   441  			}
   442  		case kinds.KindTaxonomy, kinds.KindTerm:
   443  
   444  			if p.m.singular != "" {
   445  				section = p.m.singular
   446  			} else if len(sections) > 0 {
   447  				section = sections[0]
   448  			}
   449  		default:
   450  		}
   451  
   452  		p.layoutDescriptor = layouts.LayoutDescriptor{
   453  			Kind:    p.Kind(),
   454  			Type:    p.Type(),
   455  			Lang:    p.Language().Lang,
   456  			Layout:  p.Layout(),
   457  			Section: section,
   458  		}
   459  	})
   460  
   461  	return p.layoutDescriptor
   462  }
   463  
   464  func (p *pageState) resolveTemplate(layouts ...string) (tpl.Template, bool, error) {
   465  	f := p.outputFormat()
   466  
   467  	d := p.getLayoutDescriptor()
   468  
   469  	if len(layouts) > 0 {
   470  		d.Layout = layouts[0]
   471  		d.LayoutOverride = true
   472  	}
   473  
   474  	return p.s.Tmpl().LookupLayout(d, f)
   475  }
   476  
   477  // Must be run after the site section tree etc. is built and ready.
   478  func (p *pageState) initPage() error {
   479  	if _, err := p.init.Do(context.Background()); err != nil {
   480  		return err
   481  	}
   482  	return nil
   483  }
   484  
   485  func (p *pageState) renderResources() error {
   486  	var initErr error
   487  
   488  	p.resourcesPublishInit.Do(func() {
   489  		for _, r := range p.Resources() {
   490  			if _, ok := r.(page.Page); ok {
   491  				// Pages gets rendered with the owning page but we count them here.
   492  				p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages)
   493  				continue
   494  			}
   495  
   496  			src, ok := r.(resource.Source)
   497  			if !ok {
   498  				initErr = fmt.Errorf("resource %T does not support resource.Source", src)
   499  				return
   500  			}
   501  
   502  			if err := src.Publish(); err != nil {
   503  				if !herrors.IsNotExist(err) {
   504  					p.s.Log.Errorf("Failed to publish Resource for page %q: %s", p.pathOrTitle(), err)
   505  				}
   506  			} else {
   507  				p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Files)
   508  			}
   509  		}
   510  	})
   511  
   512  	return initErr
   513  }
   514  
   515  func (p *pageState) AlternativeOutputFormats() page.OutputFormats {
   516  	f := p.outputFormat()
   517  	var o page.OutputFormats
   518  	for _, of := range p.OutputFormats() {
   519  		if of.Format.NotAlternative || of.Format.Name == f.Name {
   520  			continue
   521  		}
   522  
   523  		o = append(o, of)
   524  	}
   525  	return o
   526  }
   527  
   528  type renderStringOpts struct {
   529  	Display string
   530  	Markup  string
   531  }
   532  
   533  var defaultRenderStringOpts = renderStringOpts{
   534  	Display: "inline",
   535  	Markup:  "", // Will inherit the page's value when not set.
   536  }
   537  
   538  func (p *pageMeta) wrapError(err error, sourceFs afero.Fs) error {
   539  	if err == nil {
   540  		panic("wrapError with nil")
   541  	}
   542  
   543  	if p.File() == nil {
   544  		// No more details to add.
   545  		return fmt.Errorf("%q: %w", p.Path(), err)
   546  	}
   547  
   548  	return hugofs.AddFileInfoToError(err, p.File().FileInfo(), sourceFs)
   549  }
   550  
   551  // wrapError adds some more context to the given error if possible/needed
   552  func (p *pageState) wrapError(err error) error {
   553  	return p.m.wrapError(err, p.s.h.SourceFs)
   554  }
   555  
   556  func (p *pageState) getPageInfoForError() string {
   557  	s := fmt.Sprintf("kind: %q, path: %q", p.Kind(), p.Path())
   558  	if p.File() != nil {
   559  		s += fmt.Sprintf(", file: %q", p.File().Filename())
   560  	}
   561  	return s
   562  }
   563  
   564  func (p *pageState) getContentConverter() converter.Converter {
   565  	var err error
   566  	p.contentConverterInit.Do(func() {
   567  		markup := p.m.pageConfig.Markup
   568  		if markup == "html" {
   569  			// Only used for shortcode inner content.
   570  			markup = "markdown"
   571  		}
   572  		p.contentConverter, err = p.m.newContentConverter(p, markup)
   573  	})
   574  
   575  	if err != nil {
   576  		p.s.Log.Errorln("Failed to create content converter:", err)
   577  	}
   578  	return p.contentConverter
   579  }
   580  
   581  func (p *pageState) errorf(err error, format string, a ...any) error {
   582  	if herrors.UnwrapFileError(err) != nil {
   583  		// More isn't always better.
   584  		return err
   585  	}
   586  	args := append([]any{p.Language().Lang, p.pathOrTitle()}, a...)
   587  	args = append(args, err)
   588  	format = "[%s] page %q: " + format + ": %w"
   589  	if err == nil {
   590  		return fmt.Errorf(format, args...)
   591  	}
   592  	return fmt.Errorf(format, args...)
   593  }
   594  
   595  func (p *pageState) outputFormat() (f output.Format) {
   596  	if p.pageOutput == nil {
   597  		panic("no pageOutput")
   598  	}
   599  	return p.pageOutput.f
   600  }
   601  
   602  func (p *pageState) parseError(err error, input []byte, offset int) error {
   603  	pos := posFromInput("", input, offset)
   604  	return herrors.NewFileErrorFromName(err, p.File().Filename()).UpdatePosition(pos)
   605  }
   606  
   607  func (p *pageState) pathOrTitle() string {
   608  	if p.File() != nil {
   609  		return p.File().Filename()
   610  	}
   611  
   612  	if p.Path() != "" {
   613  		return p.Path()
   614  	}
   615  
   616  	return p.Title()
   617  }
   618  
   619  func (p *pageState) posFromInput(input []byte, offset int) text.Position {
   620  	return posFromInput(p.pathOrTitle(), input, offset)
   621  }
   622  
   623  func (p *pageState) posOffset(offset int) text.Position {
   624  	return p.posFromInput(p.m.content.mustSource(), offset)
   625  }
   626  
   627  // shiftToOutputFormat is serialized. The output format idx refers to the
   628  // full set of output formats for all sites.
   629  // This is serialized.
   630  func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
   631  	if err := p.initPage(); err != nil {
   632  		return err
   633  	}
   634  
   635  	if len(p.pageOutputs) == 1 {
   636  		idx = 0
   637  	}
   638  
   639  	p.pageOutputIdx = idx
   640  	p.pageOutput = p.pageOutputs[idx]
   641  	if p.pageOutput == nil {
   642  		panic(fmt.Sprintf("pageOutput is nil for output idx %d", idx))
   643  	}
   644  
   645  	// Reset any built paginator. This will trigger when re-rendering pages in
   646  	// server mode.
   647  	if isRenderingSite && p.pageOutput.paginator != nil && p.pageOutput.paginator.current != nil {
   648  		p.pageOutput.paginator.reset()
   649  	}
   650  
   651  	if isRenderingSite {
   652  		cp := p.pageOutput.pco
   653  		if cp == nil && p.reusePageOutputContent() {
   654  			// Look for content to reuse.
   655  			for i := 0; i < len(p.pageOutputs); i++ {
   656  				if i == idx {
   657  					continue
   658  				}
   659  				po := p.pageOutputs[i]
   660  
   661  				if po.pco != nil {
   662  					cp = po.pco
   663  					break
   664  				}
   665  			}
   666  		}
   667  
   668  		if cp == nil {
   669  			var err error
   670  			cp, err = newPageContentOutput(p.pageOutput)
   671  			if err != nil {
   672  				return err
   673  			}
   674  		}
   675  		p.pageOutput.setContentProvider(cp)
   676  	} else {
   677  		// We attempt to assign pageContentOutputs while preparing each site
   678  		// for rendering and before rendering each site. This lets us share
   679  		// content between page outputs to conserve resources. But if a template
   680  		// unexpectedly calls a method of a ContentProvider that is not yet
   681  		// initialized, we assign a LazyContentProvider that performs the
   682  		// initialization just in time.
   683  		if lcp, ok := (p.pageOutput.ContentProvider.(*page.LazyContentProvider)); ok {
   684  			lcp.Reset()
   685  		} else {
   686  			lcp = page.NewLazyContentProvider(func() (page.OutputFormatContentProvider, error) {
   687  				cp, err := newPageContentOutput(p.pageOutput)
   688  				if err != nil {
   689  					return nil, err
   690  				}
   691  				return cp, nil
   692  			})
   693  			p.pageOutput.contentRenderer = lcp
   694  			p.pageOutput.ContentProvider = lcp
   695  			p.pageOutput.PageRenderProvider = lcp
   696  			p.pageOutput.TableOfContentsProvider = lcp
   697  		}
   698  	}
   699  
   700  	return nil
   701  }
   702  
   703  var (
   704  	_ page.Page         = (*pageWithOrdinal)(nil)
   705  	_ collections.Order = (*pageWithOrdinal)(nil)
   706  	_ pageWrapper       = (*pageWithOrdinal)(nil)
   707  )
   708  
   709  type pageWithOrdinal struct {
   710  	ordinal int
   711  	*pageState
   712  }
   713  
   714  func (p pageWithOrdinal) Ordinal() int {
   715  	return p.ordinal
   716  }
   717  
   718  func (p pageWithOrdinal) page() page.Page {
   719  	return p.pageState
   720  }
   721  
   722  type pageWithWeight0 struct {
   723  	weight0 int
   724  	*pageState
   725  }
   726  
   727  func (p pageWithWeight0) Weight0() int {
   728  	return p.weight0
   729  }
   730  
   731  func (p pageWithWeight0) page() page.Page {
   732  	return p.pageState
   733  }