sphinx-typst-render 0.2.3__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/PKG-INFO +23 -1
- {sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/README.md +22 -0
- {sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/pyproject.toml +1 -1
- {sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/pyproject.toml.orig +1 -1
- {sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/src/sphinx_typst_render/__init__.py +12 -2
- {sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/src/sphinx_typst_render/_compile.py +33 -14
- {sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/src/sphinx_typst_render/_directive.py +4 -0
- {sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/LICENSE +0 -0
- {sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/src/sphinx_typst_render/_downloads.py +0 -0
- {sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/src/sphinx_typst_render/static/typst-render.css +0 -0
- {sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/src/sphinx_typst_render/static/typst-render.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sphinx-typst-render
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Render Typst sources into downloadable PDFs and inline previews in Sphinx and Jupyter Book.
|
|
5
5
|
Keywords: jupyter-book,sphinx,typst,pdf,forms,documentation
|
|
6
6
|
Author: NB-TUDelft, K.Zabłocki (Zamkorus)
|
|
@@ -88,6 +88,7 @@ Pass `:inline:` to also show a preview image and a download link in the body.
|
|
|
88
88
|
| `:class:` | none | Extra CSS classes on the wrapper. |
|
|
89
89
|
| `:inline:` | off | Also show a preview and link in the page body. |
|
|
90
90
|
| `:fillable:` | off | Add interactive form fields. See below. |
|
|
91
|
+
| `:max-length:` | from config | Characters a text field accepts. `0` strips the limit. |
|
|
91
92
|
|
|
92
93
|
### Configuration
|
|
93
94
|
|
|
@@ -98,6 +99,7 @@ Pass `:inline:` to also show a preview image and a download link in the body.
|
|
|
98
99
|
| `typst_render_stage_field_library` | `True` | Stage `capture_field.typ` into the source root. |
|
|
99
100
|
| `typst_render_source_label` | `"Source"` | Heading above the page's own downloads. |
|
|
100
101
|
| `typst_render_downloads_label` | `"Worksheets"` | Heading above the rendered PDFs. |
|
|
102
|
+
| `typst_render_field_max_length` | `10000` | Characters a text field accepts. `0` strips the limit. |
|
|
101
103
|
|
|
102
104
|
## The download menu
|
|
103
105
|
|
|
@@ -178,6 +180,26 @@ yourself.
|
|
|
178
180
|
Available helpers are `text_field`, `textarea_field`, `checkbox_field`, and
|
|
179
181
|
`radio_field`, plus the lower level `capture_field`.
|
|
180
182
|
|
|
183
|
+
## Field length
|
|
184
|
+
|
|
185
|
+
ReportLab caps every text field at 100 characters by default, which stops a
|
|
186
|
+
worksheet answer mid-sentence. This package overrides that with an explicit,
|
|
187
|
+
generous cap:
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
sphinx:
|
|
191
|
+
config:
|
|
192
|
+
typst_render_field_max_length: 10000 # 0 strips the limit entirely
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The value is written explicitly rather than omitted because **some readers
|
|
196
|
+
treat a missing `/MaxLen` as zero** and then refuse all input. Stripping it is
|
|
197
|
+
still available with `0`, for projects that have checked their readers cope.
|
|
198
|
+
Either way it is a config change, not a new release of this package. A single
|
|
199
|
+
block can override it with `:max-length:`.
|
|
200
|
+
|
|
201
|
+
Only text fields are affected. A cap on a checkbox would be meaningless.
|
|
202
|
+
|
|
181
203
|
## Caching
|
|
182
204
|
|
|
183
205
|
Output is written beside the source and skipped when nothing relevant changed.
|
|
@@ -57,6 +57,7 @@ Pass `:inline:` to also show a preview image and a download link in the body.
|
|
|
57
57
|
| `:class:` | none | Extra CSS classes on the wrapper. |
|
|
58
58
|
| `:inline:` | off | Also show a preview and link in the page body. |
|
|
59
59
|
| `:fillable:` | off | Add interactive form fields. See below. |
|
|
60
|
+
| `:max-length:` | from config | Characters a text field accepts. `0` strips the limit. |
|
|
60
61
|
|
|
61
62
|
### Configuration
|
|
62
63
|
|
|
@@ -67,6 +68,7 @@ Pass `:inline:` to also show a preview image and a download link in the body.
|
|
|
67
68
|
| `typst_render_stage_field_library` | `True` | Stage `capture_field.typ` into the source root. |
|
|
68
69
|
| `typst_render_source_label` | `"Source"` | Heading above the page's own downloads. |
|
|
69
70
|
| `typst_render_downloads_label` | `"Worksheets"` | Heading above the rendered PDFs. |
|
|
71
|
+
| `typst_render_field_max_length` | `10000` | Characters a text field accepts. `0` strips the limit. |
|
|
70
72
|
|
|
71
73
|
## The download menu
|
|
72
74
|
|
|
@@ -147,6 +149,26 @@ yourself.
|
|
|
147
149
|
Available helpers are `text_field`, `textarea_field`, `checkbox_field`, and
|
|
148
150
|
`radio_field`, plus the lower level `capture_field`.
|
|
149
151
|
|
|
152
|
+
## Field length
|
|
153
|
+
|
|
154
|
+
ReportLab caps every text field at 100 characters by default, which stops a
|
|
155
|
+
worksheet answer mid-sentence. This package overrides that with an explicit,
|
|
156
|
+
generous cap:
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
sphinx:
|
|
160
|
+
config:
|
|
161
|
+
typst_render_field_max_length: 10000 # 0 strips the limit entirely
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The value is written explicitly rather than omitted because **some readers
|
|
165
|
+
treat a missing `/MaxLen` as zero** and then refuse all input. Stripping it is
|
|
166
|
+
still available with `0`, for projects that have checked their readers cope.
|
|
167
|
+
Either way it is a config change, not a new release of this package. A single
|
|
168
|
+
block can override it with `:max-length:`.
|
|
169
|
+
|
|
170
|
+
Only text fields are affected. A cap on a checkbox would be meaningless.
|
|
171
|
+
|
|
150
172
|
## Caching
|
|
151
173
|
|
|
152
174
|
Output is written beside the source and skipped when nothing relevant changed.
|
|
@@ -5,7 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
from ._compile import
|
|
8
|
+
from ._compile import (
|
|
9
|
+
DEFAULT_MAX_LENGTH,
|
|
10
|
+
RenderRequest,
|
|
11
|
+
RenderResult,
|
|
12
|
+
render,
|
|
13
|
+
stage_field_library,
|
|
14
|
+
)
|
|
9
15
|
from ._directive import TypstDirective
|
|
10
16
|
from ._downloads import (
|
|
11
17
|
add_download_buttons,
|
|
@@ -15,6 +21,7 @@ from ._downloads import (
|
|
|
15
21
|
)
|
|
16
22
|
|
|
17
23
|
__all__ = [
|
|
24
|
+
"DEFAULT_MAX_LENGTH",
|
|
18
25
|
"RenderRequest",
|
|
19
26
|
"RenderResult",
|
|
20
27
|
"TypstDirective",
|
|
@@ -22,7 +29,7 @@ __all__ = [
|
|
|
22
29
|
"setup",
|
|
23
30
|
"stage_field_library",
|
|
24
31
|
]
|
|
25
|
-
__version__ = "0.2.
|
|
32
|
+
__version__ = "0.2.4"
|
|
26
33
|
|
|
27
34
|
STATIC_DIR = Path(__file__).parent / "static"
|
|
28
35
|
|
|
@@ -66,6 +73,9 @@ def setup(app):
|
|
|
66
73
|
app.add_config_value("typst_render_preview", "svg", "env")
|
|
67
74
|
app.add_config_value("typst_render_ppi", 144.0, "env")
|
|
68
75
|
app.add_config_value("typst_render_stage_field_library", True, "env")
|
|
76
|
+
# Characters a generated text field accepts. Set to 0 to strip the limit
|
|
77
|
+
# entirely, which some readers mis-handle as zero, so it is opt in.
|
|
78
|
+
app.add_config_value("typst_render_field_max_length", DEFAULT_MAX_LENGTH, "env")
|
|
69
79
|
# Headings inserted into the theme's download menu. The first names what
|
|
70
80
|
# the page itself is, which differs per project: a manual, a chapter, a
|
|
71
81
|
# page. An empty string leaves that group unlabelled.
|
|
@@ -17,7 +17,12 @@ OUT_DIRNAME = "_typst"
|
|
|
17
17
|
LIB_FILENAME = "capture_field.typ"
|
|
18
18
|
|
|
19
19
|
#: Bumped whenever the output layout changes, to invalidate stale caches.
|
|
20
|
-
_CACHE_VERSION =
|
|
20
|
+
_CACHE_VERSION = 3
|
|
21
|
+
|
|
22
|
+
#: Characters a generated text field accepts unless configured otherwise.
|
|
23
|
+
#: Large enough that no worksheet answer reaches it, and written explicitly
|
|
24
|
+
#: because some readers treat a missing /MaxLen as zero and refuse all input.
|
|
25
|
+
DEFAULT_MAX_LENGTH = 10000
|
|
21
26
|
|
|
22
27
|
|
|
23
28
|
@dataclass(frozen=True)
|
|
@@ -30,6 +35,9 @@ class RenderRequest:
|
|
|
30
35
|
fillable: bool = False
|
|
31
36
|
ppi: float | None = None
|
|
32
37
|
preview_page: int = 1
|
|
38
|
+
#: Characters a text field accepts. 0 removes the limit entirely, which
|
|
39
|
+
#: some readers mis-handle as zero. See _apply_field_lengths.
|
|
40
|
+
max_length: int = DEFAULT_MAX_LENGTH
|
|
33
41
|
|
|
34
42
|
|
|
35
43
|
@dataclass(frozen=True)
|
|
@@ -80,6 +88,7 @@ def _options(request: RenderRequest) -> dict[str, object]:
|
|
|
80
88
|
"fillable": request.fillable,
|
|
81
89
|
"ppi": request.ppi,
|
|
82
90
|
"page": request.preview_page,
|
|
91
|
+
"max_length": request.max_length,
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
|
|
@@ -115,33 +124,43 @@ def _fingerprint(request: RenderRequest) -> str:
|
|
|
115
124
|
return digest.hexdigest()
|
|
116
125
|
|
|
117
126
|
|
|
118
|
-
def
|
|
119
|
-
"""
|
|
127
|
+
def _apply_field_lengths(writer, max_length: int) -> None:
|
|
128
|
+
"""Set, or remove, the character cap on every text field.
|
|
129
|
+
|
|
130
|
+
Three behaviours are in play here. ReportLab's ``AcroForm.textfield()``
|
|
131
|
+
defaults to ``maxlen=100`` and typst-fillable does not override it, so a
|
|
132
|
+
field silently refuses input after 100 characters. Removing ``/MaxLen``
|
|
133
|
+
altogether is not safe either, because some readers treat a missing cap as
|
|
134
|
+
zero and then refuse all input. So the default is to write an explicit,
|
|
135
|
+
generous value, and ``max_length = 0`` drops the key for anyone who has
|
|
136
|
+
checked that their readers cope.
|
|
120
137
|
|
|
121
|
-
|
|
122
|
-
not override it, so a field silently refuses input after 100 characters.
|
|
123
|
-
A worksheet answer box that stops mid-sentence is worse than useless, and
|
|
124
|
-
there is no sensible cap for one, so remove /MaxLen rather than raise it.
|
|
138
|
+
Only text fields are touched. A cap on a checkbox would be meaningless.
|
|
125
139
|
"""
|
|
140
|
+
from pypdf.generic import NameObject, NumberObject
|
|
141
|
+
|
|
126
142
|
seen: set[int] = set()
|
|
127
143
|
|
|
128
|
-
def
|
|
144
|
+
def apply(obj) -> None:
|
|
129
145
|
if obj is None or id(obj) in seen:
|
|
130
146
|
return
|
|
131
147
|
seen.add(id(obj))
|
|
132
|
-
if "/
|
|
133
|
-
|
|
148
|
+
if obj.get("/FT") == "/Tx":
|
|
149
|
+
if max_length > 0:
|
|
150
|
+
obj[NameObject("/MaxLen")] = NumberObject(max_length)
|
|
151
|
+
elif "/MaxLen" in obj:
|
|
152
|
+
del obj["/MaxLen"]
|
|
134
153
|
for kid in obj.get("/Kids", []) or []:
|
|
135
|
-
|
|
154
|
+
apply(kid.get_object())
|
|
136
155
|
|
|
137
156
|
for page in writer.pages:
|
|
138
157
|
for annot in page.get("/Annots", []) or []:
|
|
139
|
-
|
|
158
|
+
apply(annot.get_object())
|
|
140
159
|
|
|
141
160
|
acroform = writer._root_object.get("/AcroForm")
|
|
142
161
|
if acroform:
|
|
143
162
|
for field in acroform.get("/Fields", []) or []:
|
|
144
|
-
|
|
163
|
+
apply(field.get_object())
|
|
145
164
|
|
|
146
165
|
|
|
147
166
|
def _add_form_fields(base: bytes, request: RenderRequest) -> bytes:
|
|
@@ -178,7 +197,7 @@ def _add_form_fields(base: bytes, request: RenderRequest) -> bytes:
|
|
|
178
197
|
for index, page in enumerate(reader.pages):
|
|
179
198
|
if index < len(writer.pages):
|
|
180
199
|
writer.pages[index].merge_page(page, over=False)
|
|
181
|
-
|
|
200
|
+
_apply_field_lengths(writer, request.max_length)
|
|
182
201
|
# Ask the reader to build field appearances, so a blank field is visible.
|
|
183
202
|
writer.set_need_appearances_writer(True)
|
|
184
203
|
|
{sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/src/sphinx_typst_render/_directive.py
RENAMED
|
@@ -50,6 +50,7 @@ class TypstDirective(SphinxDirective):
|
|
|
50
50
|
"height": directives.length_or_unitless,
|
|
51
51
|
"ppi": directives.positive_int,
|
|
52
52
|
"page": directives.positive_int,
|
|
53
|
+
"max-length": directives.nonnegative_int,
|
|
53
54
|
"class": directives.class_option,
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -79,6 +80,9 @@ class TypstDirective(SphinxDirective):
|
|
|
79
80
|
fillable="fillable" in self.options,
|
|
80
81
|
ppi=self.options.get("ppi", config.typst_render_ppi),
|
|
81
82
|
preview_page=self.options.get("page", 1),
|
|
83
|
+
max_length=self.options.get(
|
|
84
|
+
"max-length", config.typst_render_field_max_length
|
|
85
|
+
),
|
|
82
86
|
)
|
|
83
87
|
|
|
84
88
|
# An inline preview has to be an image Sphinx can see, which means it
|
|
File without changes
|
{sphinx_typst_render-0.2.3 → sphinx_typst_render-0.2.4}/src/sphinx_typst_render/_downloads.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|