Skip to content

Commit a7b957c

Browse files
authored
feat: Support PDF cover page (#9354)
1 parent c85463d commit a7b957c

File tree

10 files changed

+115
-21
lines changed

10 files changed

+115
-21
lines changed

samples/seed/articles/toc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pdfFileName: seed.pdf
2+
pdfCoverPage: ../pdf.html
23
items:
34
- name: Getting Started
45
href: docfx_getting_started.md

samples/seed/pdf.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<style>
2+
@media print {
3+
@page {
4+
margin: 0 !important;
5+
}
6+
body {
7+
-webkit-print-color-adjust: exact;
8+
-moz-print-color-adjust: exact;
9+
-ms-print-color-adjust: exact;
10+
print-color-adjust: exact;
11+
}
12+
}
13+
</style>
14+
<div style='background-color: green; width: 100%; height: 100%; display: flex; flex-direction: column'>
15+
<p style='flex: 1'></p>
16+
<h1 style='align-self: end; margin: 1rem 2rem; color: antiquewhite'>DOCFX PDF SAMPLE</h1>
17+
</div>

src/Docfx.App/PdfBuilder.cs

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ class Outline
3333

3434
public bool pdf { get; init; }
3535
public string? pdfFileName { get; init; }
36-
public string? pdfMargin { get; init; }
37-
public bool pdfPrintBackground { get; init; }
36+
public string? pdfCoverPage { get; init; }
3837
}
3938

4039
public static Task Run(BuildJsonConfig config, string configDirectory, string? outputDirectory = null)
@@ -111,24 +110,22 @@ static async Task CreatePdf(IBrowser browser, Uri outlineUrl, Outline outline, s
111110
var pageNumbers = new Dictionary<Outline, int>();
112111
var nextPageNumbers = new Dictionary<Outline, int>();
113112
var nextPageNumber = 1;
114-
var margin = outline.pdfMargin ?? "0.4in";
115113

116114
await AnsiConsole.Progress().Columns(new SpinnerColumn(), new TaskDescriptionColumn { Alignment = Justify.Left }).StartAsync(async c =>
117115
{
118116
await Parallel.ForEachAsync(pages, async (item, CancellationToken) =>
119117
{
120118
var task = c.AddTask(item.url.PathAndQuery);
121119
var page = await browser.NewPageAsync();
122-
await page.GotoAsync(item.url.ToString());
120+
var response = await page.GotoAsync(item.url.ToString());
121+
if (response is null || !response.Ok)
122+
throw new InvalidOperationException($"Failed to build PDF page [{response?.Status}]: {item.url}");
123+
123124
await page.AddScriptTagAsync(new() { Content = EnsureHeadingAnchorScript });
124125
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
125-
var bytes = await page.PdfAsync(new()
126-
{
127-
PrintBackground = outline.pdfPrintBackground,
128-
Margin = new() { Bottom = margin, Top = margin, Left = margin, Right = margin },
129-
});
126+
var bytes = await page.PdfAsync();
130127
File.WriteAllBytes(item.path, bytes);
131-
task.Value = task.MaxValue;
128+
task.StopTask();
132129
});
133130
});
134131

@@ -137,21 +134,33 @@ await Parallel.ForEachAsync(pages, async (item, CancellationToken) =>
137134

138135
IEnumerable<(string path, Uri url, Outline node)> GetPages(Outline outline)
139136
{
137+
if (!string.IsNullOrEmpty(outline.pdfCoverPage))
138+
{
139+
var url = new Uri(outlineUrl, outline.pdfCoverPage);
140+
if (url.Host == outlineUrl.Host)
141+
yield return (GetFilePath(url), url, new() { href = outline.pdfCoverPage });
142+
}
143+
140144
if (!string.IsNullOrEmpty(outline.href))
141145
{
142146
var url = new Uri(outlineUrl, outline.href);
143147
if (url.Host == outlineUrl.Host)
144-
{
145-
var id = Convert.ToHexString(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(url.ToString()))).Substring(0, 6).ToLower();
146-
var name = Regex.Replace(url.PathAndQuery, "\\W", "-").Trim('-');
147-
yield return (Path.Combine(tempDirectory, $"{name}-{id}.pdf"), url, outline);
148-
}
148+
yield return (GetFilePath(url), url, outline);
149149
}
150150

151151
if (outline.items != null)
152+
{
152153
foreach (var item in outline.items)
153154
foreach (var url in GetPages(item))
154155
yield return url;
156+
}
157+
}
158+
159+
string GetFilePath(Uri url)
160+
{
161+
var id = Convert.ToHexString(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(url.ToString()))).Substring(0, 6).ToLower();
162+
var name = Regex.Replace(url.PathAndQuery, "\\W", "-").Trim('-');
163+
return Path.Combine(tempDirectory, $"{name}-{id}.pdf");
155164
}
156165

157166
void MergePdf()
@@ -254,11 +263,14 @@ IEnumerable<BookmarkNode> CreateBookmarks(Outline[]? items, int level = 0)
254263
continue;
255264
}
256265

257-
nextPageNumber = nextPageNumbers[item];
258-
yield return new DocumentBookmarkNode(
259-
item.name, level,
260-
new(pageNumbers[item], ExplicitDestinationType.XyzCoordinates, ExplicitDestinationCoordinates.Empty),
261-
CreateBookmarks(item.items, level + 1).ToArray());
266+
if (!string.IsNullOrEmpty(item.name))
267+
{
268+
nextPageNumber = nextPageNumbers[item];
269+
yield return new DocumentBookmarkNode(
270+
item.name, level,
271+
new(pageNumbers[item], ExplicitDestinationType.XyzCoordinates, ExplicitDestinationCoordinates.Empty),
272+
CreateBookmarks(item.items, level + 1).ToArray());
273+
}
262274
}
263275
}
264276
}

templates/modern/src/docfx.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,9 @@ article {
7373
display: none;
7474
}
7575
}
76+
77+
@media print {
78+
@page {
79+
margin: .4in;
80+
}
81+
}

templates/modern/src/layout.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,18 @@ body[data-layout="landing"] {
7070
padding-bottom: $main-padding-bottom;
7171

7272
>.content {
73+
display: flex;
74+
flex-direction: column;
7375
width: 100%;
7476

7577
>:not(article) {
7678
display: none;
7779
}
7880

81+
>article {
82+
flex: 1;
83+
}
84+
7985
@include media-breakpoint-up(md) {
8086
>article [id] {
8187
scroll-margin-top: $header-height;
@@ -89,6 +95,10 @@ body[data-layout="landing"] {
8995
}
9096

9197
@media print {
98+
>main {
99+
padding: 0 !important;
100+
}
101+
92102
>header, >footer {
93103
display: none;
94104
}

test/docfx.Snapshot.Tests/SamplesTest.Seed/articles/toc.html.view.verified.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
}
6565
],
6666
"pdfFileName": "seed.pdf",
67+
"pdfCoverPage": "../pdf.html",
6768
"_appName": "Seed",
6869
"_appTitle": "docfx seed website",
6970
"_enableSearch": true,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"content": "{\"items\":[{\"name\":\"Getting Started\",\"href\":\"docfx_getting_started.html\",\"topicHref\":\"docfx_getting_started.html\"},{\"name\":\"Engineering Docs\",\"items\":[{\"name\":\"Section 1\"},{\"name\":\"Engineering Guidelines\",\"href\":\"engineering_guidelines.html\",\"topicHref\":\"engineering_guidelines.html\"},{\"name\":\"CSharp Coding Standards\",\"href\":\"csharp_coding_standards.html\",\"topicHref\":\"csharp_coding_standards.html\"}],\"expanded\":true},{\"name\":\"Markdown\",\"href\":\"markdown.html\",\"topicHref\":\"markdown.html\"},{\"name\":\"Microsoft Docs\",\"href\":\"https://docs.microsoft.com/en-us/\",\"topicHref\":\"https://docs.microsoft.com/en-us/\"}],\"pdfFileName\":\"seed.pdf\",\"pdf\":true}"
2+
"content": "{\"items\":[{\"name\":\"Getting Started\",\"href\":\"docfx_getting_started.html\",\"topicHref\":\"docfx_getting_started.html\"},{\"name\":\"Engineering Docs\",\"items\":[{\"name\":\"Section 1\"},{\"name\":\"Engineering Guidelines\",\"href\":\"engineering_guidelines.html\",\"topicHref\":\"engineering_guidelines.html\"},{\"name\":\"CSharp Coding Standards\",\"href\":\"csharp_coding_standards.html\",\"topicHref\":\"csharp_coding_standards.html\"}],\"expanded\":true},{\"name\":\"Markdown\",\"href\":\"markdown.html\",\"topicHref\":\"markdown.html\"},{\"name\":\"Microsoft Docs\",\"href\":\"https://docs.microsoft.com/en-us/\",\"topicHref\":\"https://docs.microsoft.com/en-us/\"}],\"pdfFileName\":\"seed.pdf\",\"pdfCoverPage\":\"../pdf.html\",\"pdf\":true}"
33
}

test/docfx.Snapshot.Tests/SamplesTest.Seed/articles/toc.verified.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@
3636
}
3737
],
3838
"pdfFileName": "seed.pdf",
39+
"pdfCoverPage": "../pdf.html",
3940
"pdf": true
4041
}

test/docfx.Snapshot.Tests/SamplesTest.Seed/index.verified.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,11 @@
809809
"title": "Namespace MRef | docfx seed website",
810810
"keywords": "Namespace MRef Namespaces MRef.Demo"
811811
},
812+
"pdf.html": {
813+
"href": "pdf.html",
814+
"title": "DOCFX PDF SAMPLE | docfx seed website",
815+
"keywords": "@media print { @page { margin: 0 !important; } body { -webkit-print-color-adjust: exact; -moz-print-color-adjust: exact; -ms-print-color-adjust: exact; print-color-adjust: exact; } } DOCFX PDF SAMPLE"
816+
},
812817
"restapi/contacts.html": {
813818
"href": "restapi/contacts.html",
814819
"title": "Contacts | docfx seed website",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"conceptual": "<style> \n@media print {\n @page {\n margin: 0 !important;\n }\n body {\n -webkit-print-color-adjust: exact;\n -moz-print-color-adjust: exact;\n -ms-print-color-adjust: exact;\n print-color-adjust: exact;\n }\n}\n</style>\n<div style='background-color: green; width: 100%; height: 100%; display: flex; flex-direction: column'>\n <p style='flex: 1'></p>\n <h1 style='align-self: end; margin: 1rem 2rem; color: antiquewhite'>DOCFX PDF SAMPLE</h1>\n</div>\n",
3+
"type": "Conceptual",
4+
"source": {
5+
"remote": {
6+
"path": "samples/seed/pdf.md",
7+
"branch": "main",
8+
"repo": "https://github.com/dotnet/docfx"
9+
},
10+
"startLine": 0.0,
11+
"endLine": 0.0
12+
},
13+
"path": "pdf.md",
14+
"documentation": {
15+
"remote": {
16+
"path": "samples/seed/pdf.md",
17+
"branch": "main",
18+
"repo": "https://github.com/dotnet/docfx"
19+
},
20+
"startLine": 0.0,
21+
"endLine": 0.0
22+
},
23+
"_appName": "Seed",
24+
"_appTitle": "docfx seed website",
25+
"_enableSearch": true,
26+
"pdf": true,
27+
"rawTitle": "",
28+
"title": "DOCFX PDF SAMPLE",
29+
"wordCount": 3.0,
30+
"_key": "pdf.md",
31+
"_navKey": "~/toc.yml",
32+
"_navPath": "toc.html",
33+
"_navRel": "toc.html",
34+
"_path": "pdf.html",
35+
"_rel": "",
36+
"_tocKey": "~/toc.yml",
37+
"_tocPath": "toc.html",
38+
"_tocRel": "toc.html",
39+
"_disableToc": true,
40+
"docurl": "https://github.com/dotnet/docfx/blob/main/samples/seed/pdf.md/#L1"
41+
}

0 commit comments

Comments
 (0)