Skip to content

Commit 3723fcb

Browse files
committed
Add debug PDF viewer
1 parent 242baf1 commit 3723fcb

File tree

5 files changed

+288
-10
lines changed

5 files changed

+288
-10
lines changed

config/typesetsh.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
<?php
22

33
return [
4+
/*
5+
|--------------------------------------------------------------------------
6+
| Debug mode
7+
|--------------------------------------------------------------------------
8+
|
9+
| Configuration flag allows you to enable or disable the special view that provides
10+
| additional information and error logs alongside the PDF in a PDF view.
11+
|
12+
| This feature will only work if Laravel's global debug mode is enabled.
13+
|
14+
*/
15+
16+
'debug' => true,
17+
418
/*
519
|--------------------------------------------------------------------------
620
| Allowed directories

src/Pdf/Factory.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,16 @@ class Factory
2222
*/
2323
private $view;
2424

25-
public function __construct(Typesetsh $pdf, ViewFactory $view)
25+
/**
26+
* @var bool
27+
*/
28+
private $debug;
29+
30+
public function __construct(Typesetsh $pdf, ViewFactory $view, bool $debug = false)
2631
{
2732
$this->typesetsh = $pdf;
2833
$this->view = $view;
34+
$this->debug = $debug;
2935
}
3036

3137
/**
@@ -44,6 +50,6 @@ public function make($view, $data = [], $mergeData = []): View
4450

4551
protected function viewInstance(\Illuminate\Contracts\View\View $view): View
4652
{
47-
return new View($view, $this->typesetsh);
53+
return new View($view, $this->typesetsh, $this->debug);
4854
}
4955
}

src/Pdf/View.php

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Illuminate\Contracts\Support\Responsable;
1212
use Illuminate\Contracts\View\View as HtmlView;
1313
use Illuminate\Http\Request;
14+
use Symfony\Component\HttpFoundation\Response;
1415
use Symfony\Component\HttpFoundation\StreamedResponse;
1516
use Typesetsh\LaravelWrapper\Typesetsh;
1617

@@ -31,10 +32,23 @@ class View implements Renderable, Responsable
3132
*/
3233
private $filename;
3334

34-
public function __construct(HtmlView $view, Typesetsh $pdf)
35+
/**
36+
* @var bool
37+
*/
38+
private $debug;
39+
40+
public function __construct(HtmlView $view, Typesetsh $pdf, bool $debug = false)
3541
{
3642
$this->view = $view;
3743
$this->pdf = $pdf;
44+
$this->debug = $debug;
45+
}
46+
47+
public function debug(bool $flag = true)
48+
{
49+
$this->debug = $flag;
50+
51+
return $this;
3852
}
3953

4054
public function forceDownload(string $filename): self
@@ -63,8 +77,15 @@ public function render(): string
6377
*
6478
* @param Request $request
6579
*/
66-
public function toResponse($request): StreamedResponse
80+
public function toResponse($request): Response
6781
{
82+
if ($this->debug) {
83+
return $this->toDebugResponse();
84+
}
85+
86+
$html = $this->view->render();
87+
$result = $this->pdf->render($html);
88+
6889
$cb = function () {
6990
echo $this->render();
7091
};
@@ -79,4 +100,55 @@ public function toResponse($request): StreamedResponse
79100

80101
return response()->stream($cb, 200, $headers);
81102
}
103+
104+
105+
public function toDebugResponse()
106+
{
107+
$html = $this->view->render();
108+
$result = $this->pdf->render($html);
109+
110+
$js = <<<HTML
111+
<script type="text/javascript">
112+
function emulatePrintMedia() {
113+
for (var sheet of document.styleSheets) {
114+
for (var rule of sheet.cssRules) {
115+
if (rule.type === CSSRule.MEDIA_RULE) {
116+
if (rule.conditionText === 'print') {
117+
rule.media.mediaText = 'screen';
118+
} else if (rule.conditionText === 'screen') {
119+
rule.media.mediaText = 'disabled';
120+
} else if (rule.conditionText === 'prefers-color-scheme: dark') {
121+
rule.media.mediaText = 'disabled';
122+
}
123+
}
124+
}
125+
}
126+
for (var sheet of document.getElementsByTagName("link")) {
127+
if (sheet.media === 'screen') {
128+
sheet.disabled = true;
129+
} else if (sheet.media === 'print') {
130+
sheet.media = '';
131+
}
132+
}
133+
}
134+
135+
window.addEventListener('load', function() {
136+
emulatePrintMedia();
137+
});
138+
</script>
139+
HTML;
140+
141+
$log = [];
142+
foreach( $result->issues as $issue) {
143+
$log[] = $issue->getMessage();
144+
}
145+
146+
return response()->view('typesetsh::debug', [
147+
'result' => $result,
148+
'pdf' => $result->asString(),
149+
'html' => str_replace('<link ', '<link crossorigin ', $html).$js,
150+
'html_code' => $html,
151+
'log' => array_unique($log),
152+
]);
153+
}
82154
}

src/ServiceProvider.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,26 @@ class ServiceProvider extends Support\ServiceProvider implements Contracts\Suppo
1818
/**
1919
* Register the service provider.
2020
*/
21-
public function register()
21+
public function register(): void
2222
{
2323
$this->mergeConfigFrom(self::CONFIG_PATH, 'typesetsh');
2424

2525
$this->registerPdfRenderer();
2626
}
2727

28-
public function boot()
28+
public function boot(): void
2929
{
30+
$this->loadViewsFrom(__DIR__.'/views', 'typesetsh');
3031
$this->publishes([
3132
self::CONFIG_PATH => base_path('config/typesetsh.php'),
3233
]);
3334
}
3435

3536
protected function registerPdfRenderer(): void
3637
{
37-
$this->app->singleton('typesetsh', function ($app) {
38+
$this->app->singleton('typesetsh', function (Contracts\Foundation\Application $app) {
3839
$allowedDirectories = $app['config']['typesetsh.allowed_directories'] ?? [];
39-
$baseDir = $app['config']['typesetsh.base_dir'] ?? [];
40+
$baseDir = $app['config']['typesetsh.base_dir'] ?? '';
4041
$allowProtocols = $app['config']['typesetsh.allowed_protocols'] ?? [];
4142
$cacheDir = $app['config']['typesetsh.cache_dir'] ?? null;
4243
$timeout = (int) ($app['config']['typesetsh.timeout'] ?? 15);
@@ -59,13 +60,17 @@ protected function registerPdfRenderer(): void
5960
return new Typesetsh(new UriResolver($schemes, $baseDir), null, $pdfVersion);
6061
});
6162

62-
$this->app->singleton('typesetsh.pdf', function ($app) {
63-
return new Pdf\Factory($app['typesetsh'], $app['view']);
63+
$this->app->singleton('typesetsh.pdf', function (Contracts\Foundation\Application $app) {
64+
$debug = $app->hasDebugModeEnabled() && ($app['config']['typesetsh.debug'] ?? true);
65+
66+
return new Pdf\Factory($app['typesetsh'], $app['view'], $debug);
6467
});
6568
}
6669

6770
/**
6871
* Get the services provided by the provider.
72+
*
73+
* @return list<string>
6974
*/
7075
public function provides(): array
7176
{

src/views/debug.blade.php

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<!DOCTYPE html>
2+
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>PDF preview</title>
7+
<link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">
8+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/css/ace.min.css" rel="stylesheet">
9+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/src-min-noconflict/ace.min.js"></script>
10+
<script src="https://code.jquery.com/jquery-3.7.0.min.js" crossorigin="anonymous"></script>
11+
<style>
12+
html, body {
13+
background-color: #fff;
14+
color: #636b6f;
15+
font-family: 'Nunito', sans-serif;
16+
font-weight: 200;
17+
margin: 0;
18+
}
19+
main {
20+
position: fixed;
21+
width: 100%;
22+
height: 100%;
23+
}
24+
.rows {
25+
display: flex;
26+
flex-direction: column;
27+
width: 100%;
28+
height: 100%;
29+
}
30+
.cols {
31+
display: flex;
32+
flex-direction: row;
33+
width: 100%;
34+
height: 100%;
35+
}
36+
37+
section.preview.tabs {
38+
width: 100%;
39+
height: 100%;
40+
}
41+
section.preview.tabs .tab {
42+
width: 100%;
43+
height: 100%;
44+
display: none;
45+
}
46+
section.preview.tabs .tab.active {
47+
display: block;
48+
}
49+
#editor {
50+
width: 100%;
51+
height: 100%;
52+
border: 0;
53+
}
54+
iframe {
55+
width: 100%;
56+
height: 100%;
57+
border: 0;
58+
}
59+
section.warnings {
60+
min-height: 20vh;
61+
max-height: 25vh;
62+
border-top: 5px solid #464646;
63+
background: black;;
64+
padding: 0;
65+
overflow: auto;
66+
}
67+
section.warnings ul {
68+
font-family: monospace;
69+
font-size: 11px;
70+
list-style: none;
71+
color: #77888e;
72+
padding: 0;
73+
margin: 0;
74+
}
75+
section.warnings ul li {
76+
border-bottom: 1px solid #373f42;
77+
background: #282828;;
78+
padding: 5px 10px;
79+
}
80+
section.toolbar {
81+
height: 60px;
82+
border-bottom: 2px solid #464646;
83+
background: #282828;
84+
align-content: center;
85+
align-items: center;
86+
padding: 10px;
87+
}
88+
section.toolbar .logo {
89+
height: 30px;
90+
}
91+
92+
nav.tabs {
93+
margin-left: 20px;
94+
display: flex;
95+
column-gap: 0px;
96+
align-content: center;
97+
align-items: center;
98+
height: auto;
99+
border-radius: 4px;
100+
overflow: hidden;
101+
}
102+
nav.tabs > a {
103+
display: block;
104+
padding: 5px 15px;
105+
background: #1e1e1e;
106+
color: #c4c4c4;
107+
cursor: pointer;
108+
height: 20px;
109+
font-size: 10pt;
110+
font-weight: bold;
111+
line-height: 16pt;
112+
border-right: 1px solid #282828;
113+
}
114+
nav.tabs > a:last-child {
115+
border:none;
116+
}
117+
nav.tabs > a.active {
118+
color:red;
119+
}
120+
121+
</style>
122+
</head>
123+
<body>
124+
<main class="rows">
125+
<section class="toolbar cols">
126+
<a href="https://docs.typeset.sh/setup/css-and-paged-media" class="logo" target="_blank">
127+
<img src="https://stat.typeset.sh/images/logo-square.png" alt="typeset.sh" class="logo">
128+
</a>
129+
<nav class="tabs">
130+
<a class="tab pdf" onclick="tab('pdf')">PDF</a>
131+
<a class="tab html" onclick="tab('html')">HTML</a>
132+
<a class="tab code" onclick="tab('code')">CODE</a>
133+
<a class="tab help" onclick="tab('help')">?</a>
134+
</nav>
135+
</section>
136+
<section class="preview tabs">
137+
<div class="pdf tab">
138+
<iframe id="pdf"></iframe>
139+
</div>
140+
<div class="html tab">
141+
<iframe id="html"></iframe>
142+
</div>
143+
<div class="code tab">
144+
<div id="editor">{{ $html_code }}</div>
145+
</div>
146+
<div class="help tab">
147+
<iframe src="https://docs.typeset.sh/setup/css-and-paged-media"></iframe>
148+
</div>
149+
</section>
150+
<section class="warnings">
151+
<ul>
152+
@foreach( $log as $message)
153+
<li>{{ $message }}</li>
154+
@endforeach
155+
</ul>
156+
</section>
157+
</main>
158+
<script type="text/javascript">
159+
var editor = ace.edit("editor");
160+
editor.setTheme("ace/theme/monokai");
161+
editor.session.setMode("ace/mode/html");
162+
163+
tab('pdf');
164+
165+
function tab(tab) {
166+
$('.tabs > .tab').removeClass('active').filter('.'+tab).addClass('active')
167+
}
168+
169+
function displayContent(id, type, content) {
170+
document.getElementById(id).src = "data:"+type+";base64," + content;
171+
}
172+
173+
window.addEventListener('load', function() {
174+
displayContent('pdf', 'application/pdf', '{!! base64_encode($pdf) !!}');
175+
displayContent('html', 'text/html', '{!! base64_encode($html) !!}');
176+
177+
console.log(document.getElementById('html'));
178+
});
179+
</script>
180+
</body>
181+
</html>

0 commit comments

Comments
 (0)