ggrossetie / asciidoctor-web-pdf

Convert AsciiDoc documents to PDF using web technologies
https://asciidoctor.org
MIT License
448 stars 91 forks source link

How to customize the title page with template-require? #128

Closed ThomasPrioul closed 4 years ago

ThomasPrioul commented 4 years ago

I would like to customize the title page using a template.js file, to add complicated corporate document tables containing a lot of metadata. Also the following pages footers would endure the same treatment (embedding a small table in the footer).

My understanding is that to do this, you have to override the document node in templates.js module.exports, which contains a lot of pre-defined stuff going on in there. I saw this in the source code :

const titlePage = (node) => {
  if (node.getDocumentTitle()) {
    const doc = node.getDocument()
    if (hasTitlePage(doc)) {
      return `<div id="cover" class="title-page">
  <h1>${node.getDocumentTitle()}</h1>
  <h2>${node.getDocument().getAuthor()}</h2>
</div>`
    }
    return `<div class="title-document">
  <h1>${node.getDocumentTitle()}</h1>
</div>`
  }
  return ''
}

Which is then used in the document node:

<body class="article">
${titlePage(node)}
${contentHTML}
${footnotes(node)}

Alongside other things which I would rather not want to touch to, especially if things evolve in this lib's code in the future, I'd just want to edit that specific part of the document generation.

I tried copying all the contents of the /lib folder to my document directory and then edit from there, but I'm having some trouble with npm and the resolving of all the dependencies. Forking the project does work, except I'm limited to using the alpha3 (with all my f***ing corporate PC problems) and I think it's not very practical and surely I'm missing something. My understanding of node, npm and other related things is shallow as I don't work on these technologies.

So my questions are:

  1. Can I edit/override just this const method somehow and how would you proceed to do that?
  2. And how do I customize the footer's content too by the way?
ggrossetie commented 4 years ago

That's a really good question! The short answer is that for now we do not provide an easy way to do that but I have a few ideas :wink:

The first one is that it should be possible to delegate to a "built-in" converter. The second one is that we should create an higher-level API to override specific components. For instance, the Table Of Contents or the title page.

The footer content is defined via CSS using @page:

https://github.com/Mogztter/asciidoctor-pdf.js/blob/2c1f9ee024c0a2ec3ad7e189b79a068d8120552a/css/features/title-page-numbering.css#L21

To be honest, I'm not really sure how we can define a complex layout in the footer... maybe Paged.js has a feature to do that... I need to check.

I tried copying all the contents of the /lib folder to my document directory and then edit from there, but I'm having some trouble with npm and the resolving of all the dependencies.

We are reading some assets from the css directory and from the node_modules.

Forking the project does work, except I'm limited to using the alpha3 (with all my f***ing corporate PC problems) and I think it's not very practical and surely I'm missing something.

Unfortunately, that's probably your best bet for now :disappointed:

ThomasPrioul commented 4 years ago

To be honest, I'm not really sure how we can define a complex layout in the footer... maybe Paged.js has a feature to do that... I need to check.

I found this : https://www.w3.org/TR/css-gcpm-3/#running-syntax And also read this article : https://www.pagedmedia.org/paged-media-approaches-part-1-of-2/

example-of-content-css-method See the 4th drawing there.

It appears that if you declare the header content in CSS with element() instead of the counter method, and create the necessary named HTML in the body and add the running CSS attribute to it, it will be removed from the body and put in the header instead. I'll test this after lunch 👍

ThomasPrioul commented 4 years ago

It kinda works:

table

However paged.js is giving some headache. It seems like it's adding styles to the global stylesheet through some javascript event, so its stylesheets always have priority over mine, as they always end up in the end of the style tag. Here is what it always ends up in the stylesheet:

.pagedjs_page .pagedjs_margin-bottom-center {
}
.pagedjs_page .pagedjs_margin-bottom-center>.pagedjs_margin-content>* {
 display:table
}
.pagedjs_page .pagedjs_margin-bottom-center {
}
.pagedjs_page .pagedjs_margin-bottom-center>.pagedjs_margin-content>* {
 display:block
}

The first two blocks is my attempt at moving the "customStyleContent" in the template further away like this:

module.exports = {
  document: (node) => {
    const cdnBaseUrl = `${assetUriScheme(node)}//cdnjs.cloudflare.com/ajax/libs`
    const linkcss = node.isAttribute('linkcss')
    const contentHTML = `<div id="content" class="content">
${node.getContent()}
</div>`
    const syntaxHighlighter = node['$syntax_highlighter']()
    return `<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
<meta charset="UTF-8">
<style>
${asciidoctorStyleContent}
${documentStyleContent}
${titlePageStyle(node)}
</style>
${stemContent.content(node)}
<script>
${pagedContent}
${repeatTableHeadersContent}
</script>
<style>
${customStyleContent(node)}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
${syntaxHighlighterHead(node, syntaxHighlighter, { linkcss: linkcss })}
</head>
<body class="article">
<table class="bottom-title-block">
<tbody>
  <tr>
    <td>Left content</td>
    <td>Middle content</td>
    <td>Right content</td>
  </tr>
  <tr>
    <td>Left content</td>
    <td>Middle content</td>
    <td>
      <p class="page-count">/</p>
    </td>
  </tr>
</tbody>
</table>
${titlePage(node)}
${contentHTML}
${footnotes(node)}
${syntaxHighlighterFooter(node, syntaxHighlighter, { cdn_base_url: cdnBaseUrl, linkcss: linkcss, self_closing_tag_slash: '/' })}
</body>`
  }
}

And using a custom stylesheet in the CLI with -a stylesheet=my.css

The last two blocks which override mine seem to come from paged.js.

css

Here is my custom css:

@page {
  margin: 1.5cm;
  size: A4;
  @bottom-center {
    content: element(bottom-title-block); 
  }
}

@page :left {
  @bottom-right {
    all: revert
  }
  @bottom-left {
    all: revert
  }
}

@page :right {
  @bottom-right {
    all: revert
  }
  @bottom-left {
    all: revert
  }
}

@page :first {
  @bottom-right {
    all: revert
  }
  @bottom-left {
    all: revert
  }
}

.page-count::before {
  content: counter(page);
}

.page-count::after {
  content: counter(pages);
}

table.bottom-title-block {
  table-layout: auto;
  display: table;
  width: 100%;
}

.bottom-title-block { 
   position: running(bottom-title-block); 
}

// completely useless
.pagedjs_page .pagedjs_margin-bottom-center {
}
.pagedjs_page .pagedjs_margin-bottom-center>.pagedjs_margin-content>* {
 display: table;
}
ThomasPrioul commented 4 years ago

I found out that you can actually embed raw HTML markup inside an asciidoc document. https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/ at the section called passthrough. This is a good candidate for these complex headers/footers, and doesn't involve modifying the source code of this project. However it only works in the body of the document, not the title page. Just a bit of HTML and a stylesheet. You enclose the HTML with 4 plus signs in the asciidoc. You can also use attributes in there, which is pretty awesome.

[subs=attributes]
++++
<table class="bottom-title-block">
  <col width="8*">
  <col width="2*">
  <tr>
    <td>{description}</td>
    <td>Top R</td>
  </tr>
  <tr>
    <td>Bottom left</td>
    <td>
      <a class="page-count">/</a>
    </td>
  </tr>
</table>
++++

Also I fixed my earlier problem using the !important value in CSS, so it wins against paged.js styles over the priority.

.pagedjs_page .pagedjs_margin-bottom-center>.pagedjs_margin-content>* {
    display: table !important;
}

Seems hackish to me, but it works.

image

ggrossetie commented 4 years ago

Thanks for sharing @ThomasPrioul I think we should definitely document how to use a complex layout in the footer using the running and element features.

However it only works in the body of the document, not the title page.

I don't understand the all: revert in you CSS ? What about using the following:

.title-page {
  position: running(title-page);  /* remove the default title page */
}

.my-custom-title-page {
  position: running(my-custom-title-page);
}

@page :first {
  content: element(my-custom-title-page);
}

Just a bit of HTML and a stylesheet. You enclose the HTML with 4 plus signs in the asciidoc. You can also use attributes in there, which is pretty awesome.

This passthrough should really be a temporary solution because now your document is tied to an HTML backend. But in the meantime, that's better than forking the whole project 🤷‍♂

Also I fixed my earlier problem using the !important value in CSS, so it wins against paged.js styles over the priority.

Paged.js has made some improvements recently. Maybe this default style can be disabled when the footer contains an element ? What do you think @julientaq ?

julientaq commented 4 years ago

Also I fixed my earlier problem using the !important value in CSS, so it wins against paged.js styles over the priority.

Every time you see something like this with paged.js, please trigger me. It means that there is something wrong in the lib.

so, you need to have a footer that include quite some informations.

What you need to know is that paged.js polyfill your css properites, or, translate it, so the browser can work with that. We use grid to define the page and flex to define what's inside the margin-boxes. We never add a table in there ever, so this is a new interresting use case.

i would strongy go against table for layout purposes as you can achieve almost the same stuff using grid, with less pain, but that's just an aside

So, in that case, we need to see how to handle that in paged.js

@ThomasPrioul Would you mind sharing a small bit of that HTML so we can think how to get it right? I'll open an issue on https://gitlab.pagedmedia.org/tools/pagedjs right after

Thanks for finding that one :)

ThomasPrioul commented 4 years ago

I think we should definitely document how to use a complex layout in the footer using the running and element features.

Yes I agree, once an appealing solution is found, it should be part of the examples.

I don't understand the all: revert in you CSS ?

If I omit them, your default styles add page numbers in the left/right column, which clashes with my own content which is using the middle column. Remember that your default template stacks up all the stylesheets, the default ones, the custom one + paged.js which generates styles. This ends up creating a massive amount of styles in the document.

image

Also it doesn't work in the title page, because I used the title-page attribute.

What about using the following:

.title-page {
  position: running(title-page);  /* remove the default title page */
}

.my-custom-title-page {
  position: running(my-custom-title-page);
}

@page :first {
  content: element(my-custom-title-page);
}

That probably works yes, I'll check it later.

This passthrough should really be a temporary solution because now your document is tied to an HTML backend. But in the meantime, that's better than forking the whole project 🤷‍♂

I agree, although one can use #ifdefs in asciidoc IIRC, to remove it when compiling for other backends or making a conditional include just for this output backend. It could be an acceptable compromise. I'd prefer something that really works without necesseraly touching the asciidoc files like when using attributes like toc.

ggrossetie commented 4 years ago

If I omit them, your default styles add page numbers in the left/right column, which clashes with my own content which is using the middle column.

Oh right, I forgot you're still using 1.0.0-alpha.3. In the latest version, if you use a custom stylesheet then the built-in styles are not added anymore.

I agree, although one can use #ifdefs in asciidoc IIRC, to remove it when compiling for other backends or making a conditional include just for this output backend. It could be an acceptable compromise. I'd prefer something that really works without necesseraly touching the asciidoc files like when using attributes like toc.

Fair enough 😉

ThomasPrioul commented 4 years ago

so, you need to have a footer that include quite some informations.

I'm actually trying to do a POC at work, that we can get rid of MS Word and all the problems that arise with it, and to promote Asciidoc instead. So I'm trying to mimic what our documents look like at work. I've started with one of the most used templates in A4 format: Title page: title_page

Following pages footer: other_page

You can see how much data this table is supposed to contain, and it's all document metadata that would be a breeze to use with Asciidoctor, I could pass them using attributes in the doc or directly from the CLI...

So far, these tables coming straight from hell are made with MS Word. I let you imagine all the problems related to people using different versions and how MS Word manages its stylesheets inside (a deep auto-generated mess).

So that's why I want to use tables, or grids or whatever most suitable in modern web development! I am not a web developer so I'm still struggling with some of the basics it seems.

What you need to know is that paged.js polyfill your css properites, or, translate it, so the browser can work with that. We use grid to define the page and flex to define what's inside the margin-boxes. We never add a table in there ever, so this is a new interresting use case. So, in that case, we need to see how to handle that in paged.js

Alright. Also I'm wondering how the margins are actually calculated with all of this. If I put a high footer content, I expect it to be placed in between the body of the document and the bottom margin. It seems here that with the default template the page numbers are inserted inside the "margin" boxes. I don't know if I'm making any sense. Here is a photo of the problem I'm trying to describe:

image

It could work, as long as the size of the bottom boxes resize accordingly on each page and is not of a predefined fixed size. I want the footer to always show correctly and not to bleed out, I want the body to move over to the next page sooner if that's necessary. But that seems not to really be the case right now. Maybe I'm just not using the right tool for the job. It seems the footnotes actually use the page area and not the margin boxes.

@ThomasPrioul Would you mind sharing a small bit of that HTML so we can think how to get it right? I'll open an issue on https://gitlab.pagedmedia.org/tools/pagedjs right after

Here are the relevants bits from the previous comments:

<table class="bottom-title-block">
  <col width="8*">
  <col width="2*">
  <tr>
    <td>{description}</td>
    <td>Top R</td>
  </tr>
  <tr>
    <td>Bottom left</td>
    <td>
      <a class="page-count">/</a>
    </td>
  </tr>
</table>
@page {
  margin: 1.5cm;
  size: A4;
  @bottom-center {
    content: element(bottom-title-block); 
  }
}

.page-count::before {
  content: counter(page);
}

.page-count::after {
  content: counter(pages);
}

table.bottom-title-block {
  table-layout: auto;
  display: table; /* This doesn't work because of the paged js styles */
  width: 100%;
}

.bottom-title-block { 
   position: running(bottom-title-block); 
}

/* This should not have to be used */
.pagedjs_page .pagedjs_margin-bottom-center {
}
.pagedjs_page .pagedjs_margin-bottom-center>.pagedjs_margin-content>* {
 display: table !important; /* Used hack to make it work */
}
julientaq commented 4 years ago

Thanks, i'll investigate 👍

ggrossetie commented 4 years ago

@ThomasPrioul You can now override the default converter in the latest published version https://github.com/Mogztter/asciidoctor-pdf.js/releases/tag/v1.0.0-alpha.6

ghost commented 2 years ago

@ThomasPrioul You can now override the default converter in the latest published version https://github.com/Mogztter/asciidoctor-pdf.js/releases/tag/v1.0.0-alpha.6

@Mogztter Newb here sorry if this is a stupid question, but how do I override it? I searched everywhere but still didn't find or understand how to do it

Is it similar to the described in asciidoctor.js' documentation?

ggrossetie commented 2 years ago

There are several options, so it depends on what you want to achieve and how you are calling asciidoctor-web-pdf but you can take a look at: https://github.com/Mogztter/asciidoctor-web-pdf/blob/1ed0d7ec41cde2fbad4fda94b58a0024bcb4e0ba/test/document_convert_test.js#L15-L30

ghost commented 2 years ago

And how to I set it up? My project looks like this:

project-folder
├───images
├───output
├───sections
├───styles
├───generate-pdf.bat
├───master.adoc
└───output.html

edit: I was using the pre-compile binary but now switched to the npm install and installed the dependencies, but still don't understand how to require('../lib/document/document-converter')

ggrossetie commented 2 years ago

Please join https://chat.asciidoctor.org or post your question using GitHub Discussion.

Issue tracker is reserved for bug.

Thanks!