Option sets: a .o target for shared parameters
A named group of optional parameters, declared once and used by several
klammers, so a writer learns one vocabulary instead of a spelling per
klammer. The "o" target is a pseudo-target beside "k": "k" declares a
klammer's interface and documents it, "o" declares an option interface and
documents it, and neither produces output for any target.
@@caption_args.o :caption :number.bool true :caption_side.side
: Arguments that define a caption for a block element @@
@@code.k :filename @hpos_args :hpos left @ @caption_args :caption_side top @
| text.literal : A source file displayed verbatim @@
A set is used only in the parameter list of a ".k" declaration -- the one
place a klammer's interface is declared once for all of its targets -- and
is resolved as that list is read. Names and types come from the set; a
default may be overridden where it is used. A klammer application in a
parameter list is now a definition-time error.
The SKS gains the sets caption_args and hpos_args (:hpos and :offset), and
@table, @image, @image_grid, @reference and @show gain .k declarations. A
distance is no longer written as a position: :hpos 4em is rejected, and the
same layout is :hpos left :offset 4em. Code listings are numbered by
default, like tables and figures.
New engine sources mac/option_set{,_registry}.{h,cpp}; tst/ ships two more
suites, option_set_test.sh and signature_test.sh (twelve in all).
(from dev 34e536cb0329)
This commit is contained in:
@@ -1,3 +1,26 @@
|
||||
#[
|
||||
A paragraph is a document-structural object, like a section or a table of
|
||||
contents, and making paragraphs is a service @document provides: inside a
|
||||
@document, blank-line-separated text becomes paragraphs (the LaTeX
|
||||
convention) and @par need not be written. @par is the explicit form, for
|
||||
a text FRAGMENT rendered without a @document, where nothing infers them.
|
||||
Written inside a @document it is harmless: html sees a <p>, which the
|
||||
paragraph pass recognizes as a block and leaves alone, and \par in
|
||||
vertical mode is a no-op.
|
||||
]#
|
||||
|
||||
@@par.k s : A paragraph of text @@
|
||||
@@par.html :: <p>*s*</p> @@
|
||||
@@par.tex ::
|
||||
\par
|
||||
*s*
|
||||
\par
|
||||
@@
|
||||
# In plain text a paragraph is delimited by blank lines, which
|
||||
# phases.justify_blocks then fills; #/2 inserts them without depending on
|
||||
# the definition body's own whitespace surviving extraction.
|
||||
@@par.txt :: #/2*s*#/2 @@
|
||||
|
||||
@@sp.k : Non-breaking space character @@
|
||||
@@sp.html :: &^#160; @@
|
||||
@@sp.tex :: ~ @@
|
||||
|
||||
@@ -27,7 +27,7 @@ class Indent(klammer_base.Klammer_base):
|
||||
result = tab + latex_util.minipage(
|
||||
"\\raggedright " + self.s, f"\\textwidth - {self.w}ex", center=False, vmargin="4pt")
|
||||
#print(result)
|
||||
return result
|
||||
return latex_util.block(result)
|
||||
|
||||
def txt(self):
|
||||
indent = " " * self.w
|
||||
@@ -54,13 +54,15 @@ class Note(klammer_base.Klammer_base):
|
||||
#width = r'\\textwidth - 16pt - {}\\leftmargin'.format(self.level)
|
||||
width = r'\\linewidth - \\leftmargin + 2pt'
|
||||
|
||||
# The \par on each side comes from latex_util.block() below: an
|
||||
# \fcolorbox is box material and must sit in vertical mode.
|
||||
result = '''
|
||||
\\par\\begingroup
|
||||
\\begingroup
|
||||
COLOR\\setlength{\\fboxsep}{8pt}
|
||||
\\fcolorbox{bordercolor}{localcolor}{
|
||||
\\parbox{WIDTH}{\\raggedright\\setlength{\\parskip}{8pt}
|
||||
\\textbf{LABEL:} TEXT
|
||||
}}\\endgroup\\par
|
||||
}}\\endgroup
|
||||
'''
|
||||
result = re.sub('LABEL', self.label, result)
|
||||
result = re.sub('TEXT', re.sub(r'\\', r'\\\\', self.s), result)
|
||||
@@ -68,7 +70,7 @@ COLOR\\setlength{\\fboxsep}{8pt}
|
||||
result = re.sub('COLOR', r'\\definecolor{{bordercolor}}{{rgb}}{{{}}}\n'.format(self.bordercolor), result)
|
||||
result = re.sub('WIDTH', width, result)
|
||||
print(result)
|
||||
return result
|
||||
return latex_util.block(result)
|
||||
|
||||
|
||||
class Block(klammer_base.Klammer_base):
|
||||
@@ -80,6 +82,9 @@ class Block(klammer_base.Klammer_base):
|
||||
return ""
|
||||
|
||||
def tex(self):
|
||||
# NOT latex_util.block(): a textblock is absolutely positioned and
|
||||
# does not participate in the normal flow, so breaking the paragraph
|
||||
# around it would move the surrounding text.
|
||||
to_x, to_y = [float(e) for e in self.to.split()]
|
||||
pt_x, pt_y = [float(e) for e in self.point.split()]
|
||||
result = f"""
|
||||
|
||||
122
sks/code/code.k
122
sks/code/code.k
@@ -1,128 +1,20 @@
|
||||
|
||||
@@code.k :filename :pattern :caption :number.bool | text.literal :
|
||||
@@code.k :filename :pattern
|
||||
@hpos_args :hpos left @
|
||||
@caption_args :caption_side top @
|
||||
| text.literal :
|
||||
A source file displayed verbatim
|
||||
@@
|
||||
|
||||
@@code :: @eval code_format.Code(K) @ @@
|
||||
@@code :: @eval code_block.Code(K) @ @@
|
||||
|
||||
@@c.k code_text :
|
||||
A word or phrase displayed verbatim in a line
|
||||
@@
|
||||
|
||||
@@c :: @eval code_format.Code_fragment(K) eval@
|
||||
@@c :: @eval code_block.Code_fragment(K) eval@
|
||||
@@
|
||||
|
||||
# :cwd makes the filename resolve against the DOCUMENT's directory, not
|
||||
# the directory ktext happens to run in.
|
||||
@@source_file filename : @eval :cwd *K_input_dir* code_format.Source(K) @ @@
|
||||
|
||||
|
||||
#[
|
||||
@@code.k text :file :number.bool true :caption : Code listing with formatted comments @@
|
||||
|
||||
@@code :
|
||||
@eval code_format.Code(K) eval@
|
||||
@@
|
||||
|
||||
|
||||
@@lst spec.figure_id :
|
||||
@reference *spec* | Listing @
|
||||
@@
|
||||
|
||||
|
||||
|
||||
c <text>
|
||||
code <text> :caption :filename :pattern
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------
|
||||
@@codebox.k s :color 1,1,1 :size normalsize :escapechar ^^
|
||||
:space_break_only.bool false :linenumber.bool false :scale 1.0
|
||||
:indent :standalone :vcenter :
|
||||
Verbatim text for source code preserving whitepace, surrounded by a box
|
||||
that extends to the margins @@
|
||||
|
||||
@@codebox.html ::
|
||||
@code :text *s* @
|
||||
@@
|
||||
|
||||
@@codebox.tex ::
|
||||
\definecolor{codeboxbgcolor}{rgb}{*color*}
|
||||
\setlength{\codeboxlinelength}{\linewidth - 6pt}
|
||||
\vspace*{4pt}
|
||||
\begin{lstlisting}%
|
||||
[frame=single,
|
||||
framerule=1pt,
|
||||
basicstyle=\*size*\ttfamily,
|
||||
lineskip=0pt,
|
||||
linewidth=*scale*\codeboxlinelength,
|
||||
columns=fullflexible,
|
||||
keepspaces=true,
|
||||
framesep=6pt,
|
||||
xleftmargin=6pt,
|
||||
escapechar=*escapechar*,
|
||||
breaklines=true,
|
||||
prebreak=\hbox{\large$\mapsto$},
|
||||
%postbreak={\textbf{\hbox{$\rightarrow$}}},
|
||||
rulecolor=\color{codeboxcolor},
|
||||
backgroundcolor=\color{codeboxbgcolor},
|
||||
breakatwhitespace=*space_break_only*,
|
||||
numbers=none, # #- @if *linenumber* | left | none @ #- ,
|
||||
numbersep=12pt,
|
||||
numberstyle=\small\color{Darkred}]
|
||||
*s*
|
||||
\end{lstlisting}
|
||||
@@
|
||||
|
||||
@@codebox.txt ::
|
||||
@code :text *s* @
|
||||
@@
|
||||
|
||||
@@pathname.k s :small.bool false : Pathname @@
|
||||
@@pathname :: @eval code_format.Pathname(K) eval@ @@
|
||||
|
||||
@@annotate.k text :caption :
|
||||
Comments put in boxes to the right of the code
|
||||
@@
|
||||
|
||||
# @@annotate : @eval code_format.Annotate(K) eval@ @@
|
||||
# @@annotate : @codebox *text* @ @@
|
||||
@@annotate :: @code :text *text @ @@
|
||||
|
||||
@@listing s : @code :text *s* @ @@
|
||||
|
||||
# --------------------------------------------------------------------------------
|
||||
@@sv.k s : Italic font for variable in @t syntax @ argument @@
|
||||
@@sv.tex :: ^^textrm"^^textit"*s*$$ @@
|
||||
@@sv.html :: @ri *s* @ @@
|
||||
|
||||
@@svs.k s : Sans-serif font for variable in @t syntax @ argument @@
|
||||
@@svs.tex :: "^^small^^textsf"^^textit"*s*$$$ @@
|
||||
@@svs.html :: @s @i *s* @ @ @@
|
||||
|
||||
@@svsub.k base | sub : Italic font for subscripted variable in @t syntax @ argument @@
|
||||
@@svsub.tex :: ^^textrm"^^textit"*base*$$^^textsubscript"*sub*$ @@
|
||||
@@svsub.html :: <span class="ritalic">*base*</span><sub>*sub*</sub> @@
|
||||
|
||||
@@syntax.k s :fontsize normalsize :indent.bool true :
|
||||
Verbatim text that includes italic font for syntax descriptions @@
|
||||
|
||||
@@syntax.html ::
|
||||
@code :text *s* @ # :indent *indent* @
|
||||
@@
|
||||
|
||||
@@syntax.tex ::
|
||||
\vspace*{8pt}\begin{LVerbatim}[xleftmargin=0pt, # @if true | 0pt | -24pt @ ,
|
||||
baselinestretch=1.05, fontsize=\*fontsize*, frame=single, framesep=8pt,
|
||||
commandchars=\\̈\$, fontfamily = @verbatimfont@, framesep=12pt]
|
||||
*s*
|
||||
\end{LVerbatim}
|
||||
@@
|
||||
|
||||
@@svspace.tex length :
|
||||
^^vspace*"*length*$
|
||||
@@
|
||||
|
||||
@@svspace.html length :
|
||||
@@
|
||||
]#
|
||||
@@source_file filename : @eval :cwd *K_input_dir* code_block.Source(K) @ @@
|
||||
|
||||
361
sks/code/code_block.py
Normal file
361
sks/code/code_block.py
Normal file
@@ -0,0 +1,361 @@
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
sys.path.append("../kutil")
|
||||
sys.path.append("../target")
|
||||
|
||||
import sys
|
||||
import re
|
||||
import klammer_base
|
||||
import kutil
|
||||
import html_util
|
||||
from html_util import E
|
||||
import latex_util as L
|
||||
import pprint
|
||||
import phases
|
||||
|
||||
def escape_newlines(s):
|
||||
# A newline becomes the marker the html paragraph pass turns back into a
|
||||
# line break, so a verbatim source file keeps its lines. Used by Source.
|
||||
return re.sub("\n", " ___NL___ ", s)
|
||||
|
||||
def undash(s):
|
||||
# Verbatim text must show the hyphens the writer typed: the target's
|
||||
# "--"/"---" transforms have already run, so put them back. Used by
|
||||
# Code_fragment.
|
||||
result = re.sub("__MDASH__", "---", s)
|
||||
return re.sub("__NDASH__", "--", result)
|
||||
|
||||
def is_comment(s):
|
||||
return s.strip().startswith("//")
|
||||
|
||||
def split_blocks(s):
|
||||
kutil.msg()
|
||||
blocks = []
|
||||
in_code = True
|
||||
block = ""
|
||||
for line in s.rstrip().split("\n"):
|
||||
if is_comment(line):
|
||||
if in_code:
|
||||
blocks.append(block)
|
||||
block = line + "\n"
|
||||
in_code = False
|
||||
else:
|
||||
block += line + "\n"
|
||||
else:
|
||||
if not in_code:
|
||||
blocks.append(block)
|
||||
block = line + "\n"
|
||||
in_code = True
|
||||
else:
|
||||
block += line + "\n"
|
||||
if block:
|
||||
blocks.append(block)
|
||||
return blocks
|
||||
|
||||
def get_lines(comment, code, n):
|
||||
kutil.msg()
|
||||
lines = code.split("\n")
|
||||
if len(lines) < n:
|
||||
raise Exception(
|
||||
f"Error in @code block comment:\n{comment}\n{code}\nNeed {n} lines, but there are only {len(lines)}")
|
||||
if len(lines) == n:
|
||||
return (code, "")
|
||||
else:
|
||||
return ("\n".join(lines[:n]), "\n".join(lines[n:]))
|
||||
|
||||
def parse_blocks(blocks):
|
||||
kutil.msg()
|
||||
box_comment_rgx = re.compile("\\s*//(\\d+)\\s+.*", re.S)
|
||||
i = 0
|
||||
block_pairs = []
|
||||
while i < len(blocks):
|
||||
match = box_comment_rgx.match(blocks[i])
|
||||
if match:
|
||||
boxed_code, rest_code = get_lines(blocks[i], blocks[i+1], int(match.group(1)))
|
||||
block_pairs.append([boxed_code, blocks[i]])
|
||||
block_pairs.append([rest_code, ""])
|
||||
i += 2
|
||||
else:
|
||||
block_pairs.append([blocks[i], ""])
|
||||
i += 1
|
||||
return block_pairs
|
||||
|
||||
|
||||
# Characters that are special to LATEX but not to Klammertext. # and ^ are
|
||||
# deliberately ABSENT: they are Klammertext specials and are handled by
|
||||
# quote_specials() below, not here -- see tex_line().
|
||||
latex_escapes = {
|
||||
"\\": "\\textbackslash{}", "{": "\\{", "}": "\\}", "%": "\\%",
|
||||
"$": "\\$", "&": "\\&", "_": "\\_", "~": "\\textasciitilde{}",
|
||||
"<": "\\textless{}", ">": "\\textgreater{}",
|
||||
}
|
||||
latex_escape_rgx = re.compile("[" + re.escape("".join(latex_escapes)) + "]")
|
||||
|
||||
def escape_latex(s):
|
||||
r"""Escape LaTeX special characters in code text.
|
||||
|
||||
ONE pass, not a sequence of str.replace() calls: a sequence corrupts
|
||||
the replacements it has already made. Replacing \ first yields
|
||||
\textbackslash{}, whose braces the later { and } replacements then
|
||||
escape in turn -- so a C++ "\n" typesets as \{}n. A single regex pass
|
||||
over the original string cannot revisit its own output.
|
||||
"""
|
||||
return latex_escape_rgx.sub(lambda m: latex_escapes[m.group()], s)
|
||||
|
||||
def quote_specials(s):
|
||||
r"""Quote the Klammertext special characters in code text.
|
||||
|
||||
An @eval result is RE-READ as Klammertext, so a special character that
|
||||
reaches the result raw is interpreted again: a # in "#include" starts a
|
||||
text removal and silently eats the rest of the line. Quoting is the
|
||||
fix, and it must be quoting rather than LaTeX escaping -- \# would put
|
||||
the # back and be eaten in turn.
|
||||
|
||||
The quoted forms resolve at final processing: the tex target declares
|
||||
^# -> \# and ^^ -> \textasciicircum{} in its :escape list, and any
|
||||
other quoted special decodes back to its own character. So the target,
|
||||
not this code, decides what a # becomes in LaTeX.
|
||||
|
||||
^ is quoted FIRST, since the other quotings introduce ^ characters.
|
||||
"""
|
||||
for ch in "^@#|":
|
||||
s = s.replace(ch, "^" + ch)
|
||||
return s
|
||||
|
||||
def tex_line(line):
|
||||
r"""One code line, ready to be a \klline argument.
|
||||
|
||||
Three steps in this order: LaTeX-escape the characters only LaTeX cares
|
||||
about; quote the ones Klammertext would re-interpret (the escapes above
|
||||
introduce none of them, so the two passes cannot interfere); then make
|
||||
every space a ~ -- a non-breaking space, exactly one character wide in
|
||||
a monospace font, which LaTeX will not collapse -- so the code's
|
||||
indentation and internal alignment survive.
|
||||
"""
|
||||
return quote_specials(escape_latex(line)).replace(" ", "~")
|
||||
|
||||
def widest_line(lines):
|
||||
r"""The line to measure the block's width with.
|
||||
|
||||
Measured on the RAW lines: in a monospace font character count is
|
||||
exact, whereas the escaped text is longer than it typesets
|
||||
(\textbackslash{} is eleven characters and one glyph). The chosen
|
||||
line is escaped afterwards, for \settowidth.
|
||||
"""
|
||||
return max(lines, key=len) if lines else ""
|
||||
|
||||
def tex_block(code, comment):
|
||||
r"""One block: its lines, shaded if the block is annotated, beside its
|
||||
comment. See the "Annotated code listings" section of sty/code.sty,
|
||||
which owns the layout; this only supplies the three arguments."""
|
||||
lines = code.strip("\n").split("\n")
|
||||
if not any(line.strip() for line in lines):
|
||||
return ""
|
||||
# Every line is a plain \klline; whether the BLOCK is shaded is decided
|
||||
# by \klcodebox from the comment, because the shading is one box around
|
||||
# the block (that is what gives it vertical padding).
|
||||
body = "\\\\\n".join(f"\\klline{{{tex_line(line)}}}" for line in lines)
|
||||
return ("\\klblock{" + tex_line(widest_line(lines)) + "}{%\n"
|
||||
+ body + "}{" + comment + "}\n")
|
||||
|
||||
def comment_text(comment):
|
||||
"""The prose of a block comment: the // and the line count removed."""
|
||||
return re.sub(r"^\s*//\d*\s*", "", comment.strip(), flags=re.S).strip()
|
||||
|
||||
html_escapes = {"&": "&", "<": "<", ">": ">"}
|
||||
html_escape_rgx = re.compile("[" + re.escape("".join(html_escapes)) + "]")
|
||||
|
||||
def html_line(text):
|
||||
"""Code text as html: the markup characters escaped, then the
|
||||
Klammertext specials quoted for the @eval read-back (same reason as
|
||||
tex_line -- a raw # in "#include" would start a text removal). The
|
||||
html entities introduce no Klammertext special, so the two passes
|
||||
cannot interfere. No target :escape entries apply here, so each
|
||||
quoted special decodes back to its own character."""
|
||||
return quote_specials(html_escape_rgx.sub(
|
||||
lambda m: html_escapes[m.group()], text))
|
||||
|
||||
def html_block(code, comment):
|
||||
r"""One block: its lines beside its comment.
|
||||
|
||||
The layout is CSS (sks/code/css/code.css): .code_block is a flex row
|
||||
with align-items center -- the same model as the LaTeX \parbox[c]
|
||||
pair -- and .code_text is an inline-block with white-space: pre, so
|
||||
its shrink-to-fit width IS the block's longest line and the shading
|
||||
is one solid rectangle with no per-line work. Nothing is measured
|
||||
here: unlike LaTeX, the browser does the layout.
|
||||
"""
|
||||
lines = code.strip("\n").split("\n")
|
||||
if not any(line.strip() for line in lines):
|
||||
return ""
|
||||
shade = "code_border" if comment else "code_no_border"
|
||||
result = f'<div class="code_text {shade}">{html_line(code.strip(chr(10)))}</div>'
|
||||
if comment:
|
||||
result += f'<div class="code_comment">{comment}</div>'
|
||||
return f'<div class="code_block">{result}</div>\n'
|
||||
|
||||
|
||||
class Code(klammer_base.Klammer_base):
|
||||
id = 0
|
||||
def __init__(self, K):
|
||||
super().__init__(K)
|
||||
self.text = phases.expand_whitespace_markers(self.text)
|
||||
|
||||
def annotated(self, pairs):
|
||||
"""Does any block of this listing carry a comment?"""
|
||||
return any(comment.strip() for _, comment in pairs)
|
||||
|
||||
def tex(self):
|
||||
# No table: comments sit a fixed distance from their own block and
|
||||
# deliberately do not align with each other, and each block's box is
|
||||
# as wide as that block's longest line — so there is no column to
|
||||
# align and nothing for a table to do. The layout is in
|
||||
# sty/code.sty; see its "Annotated code listings" section.
|
||||
#
|
||||
# This is a RENDERER (final LaTeX, no klammers in the result), so
|
||||
# the Klammermachine leaves it alone. It must never emit @code:
|
||||
# a klammer that generates itself re-enters its own body with no
|
||||
# base case, which the depth guard catches at 200 levels.
|
||||
pairs = parse_blocks(split_blocks(self.text))
|
||||
blocks = "".join(tex_block(code, comment_text(comment))
|
||||
for code, comment in pairs)
|
||||
listing = "\\begin{klcode}\n" + blocks + "\\end{klcode}\n"
|
||||
captioned = bool(self.number or self.caption)
|
||||
annotated = self.annotated(pairs)
|
||||
inset = L.offset_length(self.offset)
|
||||
|
||||
# A caption does NOT by itself require a box, and boxing a listing
|
||||
# costs page breaking -- a minipage cannot break, and listings are
|
||||
# often long. Only two things genuinely need the shared box that
|
||||
# add_caption builds:
|
||||
#
|
||||
# * a caption BESIDE the listing (:caption_side left or right),
|
||||
# which has to know the listing's width;
|
||||
# * an unannotated listing that must be centered or right-aligned,
|
||||
# which has to be measured before it can be moved.
|
||||
#
|
||||
# Everything else is placed unboxed, and keeps breaking: the offset
|
||||
# becomes \klshift/\klindent inside the environment, and the caption
|
||||
# becomes a paragraph above or below, indented and width-matched to
|
||||
# the listing. An annotated listing never needs measuring -- it
|
||||
# spans the text column by construction.
|
||||
beside = captioned and self.caption_side in ("left", "right")
|
||||
must_measure = not annotated and (self.hpos != "left" or inset != "0pt")
|
||||
if beside or must_measure:
|
||||
return L.block(self.tex_boxed(listing, pairs, annotated,
|
||||
captioned, inset))
|
||||
return L.block(self.tex_unboxed(listing, annotated, captioned, inset))
|
||||
|
||||
def tex_boxed(self, listing, pairs, annotated, captioned, inset):
|
||||
r"""The listing as a box: placed and captioned like a table or an
|
||||
image, at the cost of not breaking across pages."""
|
||||
measure = ""
|
||||
if annotated:
|
||||
# It spans the text column, so an offset narrows it rather than
|
||||
# moving it; a centered element has no margin to be inset from.
|
||||
width = ("\\linewidth" if self.hpos == "center"
|
||||
else f"\\dimexpr\\linewidth-{inset}\\relax")
|
||||
else:
|
||||
# The \settowidth must stay OUTSIDE the minipage it sizes.
|
||||
lines = [line for code, _ in pairs
|
||||
for line in code.strip("\n").split("\n")]
|
||||
measure = ("\\settowidth{\\kllistingwidth}{\\ttfamily "
|
||||
+ tex_line(widest_line(lines)) + "}\n")
|
||||
width = "\\kllistingwidth"
|
||||
boxed = L.minipage(listing, width, vertical="t", center=False)
|
||||
if captioned:
|
||||
# add_caption attaches the caption to the box and hands the pair
|
||||
# to caption_wrapper, so :pos and :offset move both together.
|
||||
return measure + L.add_caption(
|
||||
boxed, "Listing", self.number, self.caption, width,
|
||||
hpos=self.hpos, side=self.caption_side,
|
||||
font_symbol=self.caption_font,
|
||||
font_size=self.caption_font_size, offset=self.offset)
|
||||
return measure + L.caption_wrapper(boxed, self.hpos,
|
||||
offset=self.offset)
|
||||
|
||||
def tex_unboxed(self, listing, annotated, captioned, inset):
|
||||
r"""The listing in the running vertical list, so it can break across
|
||||
pages. The offset is \leftskip plus a matching reduction of the
|
||||
width \klblock computes its comment column from; the caption is a
|
||||
paragraph on the same indent and width."""
|
||||
shift = inset if self.hpos == "left" else "0pt"
|
||||
# \hpos center cannot move a full-width listing, so it takes no
|
||||
# offset -- the same rule the boxed path and caption_wrapper use.
|
||||
indent = "0pt" if (annotated and self.hpos == "center") else inset
|
||||
setup = (f"\\setlength{{\\klshift}}{{{shift}}}"
|
||||
f"\\setlength{{\\klindent}}{{{indent}}}\n")
|
||||
result = setup + listing
|
||||
if captioned:
|
||||
caption = L.make_caption_text(
|
||||
self.number, "Listing", self.caption,
|
||||
self.caption_font, self.caption_font_size)
|
||||
# \nobreak: a caption must not be separated from its listing by
|
||||
# a page break, even though the listing itself may break.
|
||||
block = (f"\\noindent\\hspace*{{{shift}}}"
|
||||
f"\\parbox[t]{{\\dimexpr\\linewidth-{indent}\\relax}}"
|
||||
f"{{{caption}}}\\par")
|
||||
if self.caption_side == "top":
|
||||
result = block + "\\nobreak\n" + result
|
||||
else:
|
||||
result = result + "\\nobreak\n" + block + "\n"
|
||||
return result
|
||||
|
||||
def html(self):
|
||||
pairs = parse_blocks(split_blocks(self.text))
|
||||
result = "".join(html_block(code, comment_text(comment))
|
||||
for code, comment in pairs)
|
||||
# The same two cases as tex_place, with the browser doing the work.
|
||||
# An annotated listing's rows must stay full width so the comment
|
||||
# column's flex: 1 has a remainder to take; an unannotated one gets
|
||||
# width: fit-content, which is what lets the position container
|
||||
# center or right-align it.
|
||||
listing = E("div").cls("code_listing").body(result)
|
||||
if not self.annotated(pairs):
|
||||
listing.cls("code_listing_box")
|
||||
# As in tex: the caption goes through the shared helper, so it sits
|
||||
# in the same position container as the listing and moves with it.
|
||||
if self.number or self.caption:
|
||||
return html_util.add_caption(
|
||||
listing, "Listing", self.number, self.caption,
|
||||
self.caption_font, self.hpos, self.caption_side, True,
|
||||
self.caption_font_size, offset=self.offset)
|
||||
return html_util.hpos_container(listing, self.hpos, self.offset).str()
|
||||
|
||||
|
||||
class Code_fragment(klammer_base.Klammer_base):
|
||||
def __init__(self, K):
|
||||
super().__init__(K)
|
||||
|
||||
def html(self):
|
||||
#print(f"code: |{self.code_text}|")
|
||||
result = self.code_text.strip()
|
||||
result = undash(result)
|
||||
#result = re.escape(result)
|
||||
result = re.sub("<", "<", result)
|
||||
result = re.sub(" ", " ", result)
|
||||
#print(f"code: |{self.code_text}| -> |{result}|")
|
||||
return f'<span class="code">{result}</span>'
|
||||
|
||||
def tex(self):
|
||||
return f"{{\\tt {self.code_text.strip()}}}"
|
||||
|
||||
|
||||
class Source(klammer_base.Klammer_base):
|
||||
def __init__(self, K):
|
||||
super().__init__(K)
|
||||
with open(self.filename) as fp:
|
||||
self.src = fp.read()
|
||||
|
||||
def tex(self):
|
||||
result = self.src
|
||||
# result = re.sub("#", "^#", result)
|
||||
# result = re.sub("\\^", "\\^", result)
|
||||
result = f"\\begin{{verbatim}}\n{result}\n\\end{{verbatim}}\n"
|
||||
return result
|
||||
|
||||
def html(self):
|
||||
result = escape_newlines(self.src.strip()) + "\n"
|
||||
result = re.sub("@", "^@", result)
|
||||
result = E("div").body(result).cls("code_text").str()
|
||||
return result
|
||||
@@ -1,3 +1,5 @@
|
||||
print("DEPRECATED")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
sys.path.append("../kutil")
|
||||
@@ -22,6 +24,49 @@ def escape_newlines(s):
|
||||
# backslash_pat = re.compile(r'(".*?)\n(.*?")', re.S)
|
||||
# return backslash_pat.sub(replace, s)
|
||||
|
||||
def is_comment(s):
|
||||
return s.strip().startswith("//")
|
||||
|
||||
def split_blocks(s):
|
||||
print('-'*40)
|
||||
print(s)
|
||||
print('-'*40)
|
||||
blocks = []
|
||||
in_code = True
|
||||
block = ""
|
||||
for line in s.rstrip().split("\n"):
|
||||
if is_comment(line):
|
||||
if in_code:
|
||||
blocks.append(block)
|
||||
block = line + "\n"
|
||||
in_code = False
|
||||
else:
|
||||
block += line + "\n"
|
||||
else:
|
||||
if not in_code:
|
||||
blocks.append(block)
|
||||
block = line + "\n"
|
||||
in_code = True
|
||||
else:
|
||||
block += line + "\n"
|
||||
if block:
|
||||
blocks.append(block)
|
||||
for block in blocks:
|
||||
print("B:")
|
||||
print(block)
|
||||
return blocks
|
||||
|
||||
def parse_blocks(blocks):
|
||||
box_comment_rgx = re.compile("\s*//(\d+)\s+.*", re.S)
|
||||
i = 0
|
||||
while i < len(blocks):
|
||||
match = box_comment_rgx.match(blocks[i])
|
||||
if match:
|
||||
print(match.group(1))
|
||||
i += 1
|
||||
|
||||
|
||||
|
||||
def get_blocks(s):
|
||||
comment_pat = re.compile(r"(\s*)//(\d+)\s+(.*)", re.S)
|
||||
blocks = []
|
||||
@@ -134,7 +179,27 @@ class Code(klammer_base.Klammer_base):
|
||||
s = s.replace(">", "\\textgreater{}")
|
||||
return s
|
||||
|
||||
def code_box(self, text):
|
||||
kutil.msg(text)
|
||||
result = ""
|
||||
for line in text.rstrip().split("\n"):
|
||||
indent = len(line) - len(line.lstrip())
|
||||
eline = ("~" * indent) + line[indent:]
|
||||
print(indent, line)
|
||||
print(eline)
|
||||
result += eline + "\\\\\n"
|
||||
result = result[:-3]
|
||||
print("RESULT:")
|
||||
print(result)
|
||||
return result
|
||||
|
||||
def tex(self):
|
||||
parse_blocks(split_blocks(self.text))
|
||||
return ""
|
||||
|
||||
|
||||
|
||||
|
||||
# Escape Klammertext special characters
|
||||
self.text = self.text.replace("^", "^^")
|
||||
self.text = self.text.replace("#", "^#")
|
||||
@@ -153,6 +218,14 @@ class Code(klammer_base.Klammer_base):
|
||||
result = ""
|
||||
count = len(self.blocks)
|
||||
for text, comment in self.blocks:
|
||||
print("TEXT:")
|
||||
print(text)
|
||||
print("COMMENT:")
|
||||
print(comment)
|
||||
|
||||
|
||||
|
||||
|
||||
text = " " + re.sub("\n", " \n ", text) + " "
|
||||
longest = longest_line(text)
|
||||
text = latex_spaces(text)
|
||||
@@ -160,11 +233,23 @@ class Code(klammer_base.Klammer_base):
|
||||
text = f"{start_strut}\\ttfamily {text}{end_strut}"
|
||||
width = f"\\widthof{{\\ttfamily {longest}}}"
|
||||
code = L.environment("minipage", text, width) + "\\\\\n"
|
||||
print(code)
|
||||
|
||||
#code = "\\asymbox{" + self.code_box(text) + "}"
|
||||
#print(code)
|
||||
|
||||
code = text
|
||||
|
||||
if comment:
|
||||
width = f"\\linewidth - {width} - {indent} - {comment_sep}"
|
||||
#width = f"\\linewidth - {width} - {indent} - {comment_sep}"
|
||||
"""
|
||||
width = f"\\linewidth - \widestline - {indent} - {comment_sep}"
|
||||
code = f"\\fcolorbox{{Gray}}{{LightGray}}{{{code}}}"
|
||||
code += f"\\rule{{{comment_sep}}}{{{strutvis}}}" \
|
||||
+ L.environment("minipage", "\\sffamily\\small\\raggedright " + comment, width)
|
||||
"""
|
||||
code = "\\asymbox{" + self.code_box(text) + "}" + comment
|
||||
|
||||
result += f"\\rule{{{indent}}}{{{strutvis}}}{code}"
|
||||
if comment:
|
||||
if i < count - 1 and self.blocks[i+1][1]:
|
||||
|
||||
@@ -2,12 +2,36 @@
|
||||
font-family: var(--monospace);
|
||||
}
|
||||
|
||||
/* A whole listing, inside its :hpos position container. Full width by
|
||||
default so an annotated listing's comment column has a remainder to take
|
||||
(.code_comment is flex: 1); an UNANNOTATED listing is shrink-to-fit
|
||||
instead, which is what lets :hpos center or right-align it -- a
|
||||
full-width child cannot be moved within its container. The LaTeX
|
||||
counterpart is Code.tex_place. */
|
||||
.code_listing {
|
||||
}
|
||||
|
||||
.code_listing_box {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
/* An annotated code listing (sks/code/code_block.py). The same model as
|
||||
the LaTeX side in sty/code.sty: each BLOCK is a run of code lines with
|
||||
an optional comment beside it, the shaded box is as wide as that block's
|
||||
longest line, and comments sit a fixed distance from their own block
|
||||
without aligning with each other.
|
||||
|
||||
align-items: center is the counterpart of LaTeX's \parbox[c] pair. No
|
||||
vertical margin or padding anywhere in the stack: consecutive blocks
|
||||
must abut exactly, so that adjacent shaded blocks read as one region and
|
||||
the line rhythm is the same whether a line is shaded or not. That is
|
||||
what \strut and \offinterlineskip buy on the LaTeX side; here it is just
|
||||
the absence of vertical space. */
|
||||
.code_block {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: .125rem 0 0 1rem;
|
||||
margin: 0 0 0 1rem;
|
||||
padding: 0;
|
||||
/* flex-direction: column-reverse; */
|
||||
}
|
||||
|
||||
.code_caption {
|
||||
@@ -15,20 +39,28 @@
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* white-space: pre keeps the code's own spacing, and the shrink-to-fit
|
||||
width of an inline-block IS the block's longest line — so the shading is
|
||||
one solid rectangle with no per-line work and nothing measured. Padding
|
||||
is horizontal only; see the note on .code_block. */
|
||||
.code_text {
|
||||
display: inline-block;
|
||||
/* vertical-align: top; */
|
||||
white-space: pre;
|
||||
font-family: var(--monospace);
|
||||
line-height: 1.2;
|
||||
line-height: 1.4;
|
||||
/* No horizontal padding: the box's left edge sits ON the code column,
|
||||
where the line's own indentation begins, rather than out to the left
|
||||
of it. LaTeX counterpart: \klcodepad 0pt. */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* The comment takes what the block leaves of the containing width — the
|
||||
browser's counterpart of \dimexpr\linewidth-\klcodewidth-\klcodegap.
|
||||
min-width is the counterpart of \klcodemin: below it the comment would
|
||||
be too narrow to set prose in. */
|
||||
.code_comment {
|
||||
display: inline-block;
|
||||
/* vertical-align: top; */
|
||||
padding: .25rem;
|
||||
border: solid white 1px;
|
||||
padding: .25rem .25rem .25rem .5rem;
|
||||
flex: 1;
|
||||
padding-left: .75rem;
|
||||
font-family: var(--sans-serif);
|
||||
font-style: italic;
|
||||
font-size: .8rem;
|
||||
@@ -36,22 +68,23 @@
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
/* An annotated block: background only, no border. A border would need a
|
||||
matching one on the unshaded blocks to keep the code aligned, and the
|
||||
design settled on shading alone. */
|
||||
.code_border {
|
||||
border: solid gray 1px;
|
||||
/* margin: .125rem; */
|
||||
margin: .125rem .125rem .125rem .25rem;
|
||||
padding: .125rem .5rem .25rem .5rem;
|
||||
background-color: rgb(95%,95%,95%);
|
||||
|
||||
background-color: rgb(100%,100%,60%);
|
||||
/* Clear space above and below a boxed block, so two boxed blocks with
|
||||
no unboxed lines between them read as two boxes rather than one.
|
||||
On the block, never on the line: the lines within a block must still
|
||||
abut, or the shading stops being a solid rectangle. The LaTeX
|
||||
counterpart is \klboxgap in sty/code.sty. */
|
||||
margin: 2px 0;
|
||||
/* The box's own vertical margin: space inside the shading, above the
|
||||
first line and below the last. LaTeX counterpart: \klboxpad. */
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.code_no_border {
|
||||
padding: .125rem 0 .125rem .5rem;
|
||||
margin: 0 0 0 .125rem;
|
||||
border: solid white 1px;
|
||||
/* Debugging:
|
||||
border: solid lightgray 1px;
|
||||
background-color: rgb(250,250,127);
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@@ -7,3 +7,168 @@
|
||||
\usepackage[strings,nohyphen]{underscore}
|
||||
|
||||
\usepackage{mdframed}
|
||||
|
||||
\newsavebox{\measurebox}
|
||||
\newlength{\widestline}
|
||||
|
||||
% \measurewidest{line one\\line two\\...} -> \widestline = width of widest
|
||||
\newcommand{\measurewidest}[1]{%
|
||||
\begin{lrbox}{\measurebox}%
|
||||
\begin{tabular}{@{}l@{}}#1\end{tabular}%
|
||||
\end{lrbox}%
|
||||
\setlength{\widestline}{\wd\measurebox}%
|
||||
}
|
||||
|
||||
% \asymbox[<left pad>]{line one\\line two\\...} other three sides: 8pt
|
||||
\newcommand{\asymbox}[2][0pt]{%
|
||||
\begin{lrbox}{\measurebox}%
|
||||
\begin{tabular}{@{}l@{}}\tt #2\end{tabular}%
|
||||
\end{lrbox}%
|
||||
{\setlength{\fboxsep}{0pt}%
|
||||
\fcolorbox{black}{yellow!20}{%
|
||||
\kern#1%
|
||||
\vbox{\kern0pt\hbox{\usebox{\measurebox}}\kern0pt}%
|
||||
\kern0pt}}%
|
||||
}
|
||||
|
||||
% ===========================================================================
|
||||
% Annotated code listings. Emitted by sks/code/code_block.py; the layout
|
||||
% policy lives here so it can be tuned without touching the Python.
|
||||
%
|
||||
% The model: a listing is a vertical stack of BLOCKS, each block a run of
|
||||
% code lines with an optional comment beside it. There is deliberately no
|
||||
% table -- comments are a fixed distance from their own block and do not
|
||||
% align with each other, and each block's shaded box is as wide as that
|
||||
% block's longest line.
|
||||
%
|
||||
% Two mechanisms carry the whole appearance:
|
||||
%
|
||||
% 1. Every line is a \strut'ed \makebox of the block's width. A LaTeX
|
||||
% \strut is exactly 0.7\baselineskip high and 0.3 deep -- one full
|
||||
% \baselineskip -- so consecutive lines inside a block abut exactly,
|
||||
% with no gap and no overlap, at any point size. Without the strut a
|
||||
% line with no descender would sit closer to its neighbour and the
|
||||
% rhythm would follow the text rather than the grid.
|
||||
%
|
||||
% 2. \offinterlineskip in the listing removes the interline glue BETWEEN
|
||||
% BLOCKS, so consecutive blocks abut too and a boxed block continues
|
||||
% the rhythm of the plain lines around it. (It does not reach inside
|
||||
% a \parbox, which restores normal interline spacing -- that is why
|
||||
% the struts in point 1 are doing the work there, and why the box's
|
||||
% vertical padding comes from \fboxsep below rather than from a
|
||||
% padding line, which would be spaced off the grid.)
|
||||
% ===========================================================================
|
||||
|
||||
\definecolor{klcodeshade}{rgb}{1,1,.6}
|
||||
|
||||
\newlength{\kllistingwidth} % width of a whole unannotated listing
|
||||
% How the listing is placed WITHOUT being boxed, so that it can still break
|
||||
% across pages: \klshift moves it right (an :offset with :hpos left) and
|
||||
% \klindent is the width taken out of \linewidth (either side's offset).
|
||||
% A minipage would do both at once but cannot break -- see Code.tex in
|
||||
% sks/code/code_block.py, which sets these before \begin{klcode}.
|
||||
\newlength{\klshift} \setlength{\klshift}{0pt}
|
||||
\newlength{\klindent} \setlength{\klindent}{0pt}
|
||||
\newlength{\klcodewidth} % width of the current block's box
|
||||
\newlength{\klcommentwidth} % what is left for its comment
|
||||
% Horizontal inset of the code from the box's left edge. 0pt puts the
|
||||
% box's left edge ON the code column, where the line's own indentation
|
||||
% begins, rather than out to the left of it; a positive value moves the
|
||||
% CODE right, never the box left.
|
||||
\newlength{\klcodepad} \setlength{\klcodepad}{0pt}
|
||||
\newlength{\klcodegap} \setlength{\klcodegap}{14pt} % box to comment
|
||||
\newlength{\klcodemin} \setlength{\klcodemin}{6em} % narrower: see below
|
||||
% Clear space above and below a block that HAS a box, so that two boxed
|
||||
% blocks with no unboxed lines between them read as two boxes rather than
|
||||
% one. Applied per block, never per line: the lines within a block must
|
||||
% still abut, or the shading stops being a solid rectangle.
|
||||
% 1.5pt is 2px at the CSS reference 96dpi, matching the html margin in
|
||||
% css/code.css -- these two are meant to look the same, so change them
|
||||
% together.
|
||||
\newlength{\klboxgap} \setlength{\klboxgap}{1.5pt}
|
||||
% The box's own vertical margin: space INSIDE the shading, above the first
|
||||
% line and below the last. Same 2px equivalent, matching the html padding.
|
||||
\newlength{\klboxpad} \setlength{\klboxpad}{1.5pt}
|
||||
|
||||
% \klline{<line>} — one code line, padded and strut'ed to the block width.
|
||||
\newcommand{\klline}[1]{%
|
||||
\makebox[\klcodewidth][l]{\hspace{\klcodepad}\strut\ttfamily #1}}
|
||||
|
||||
% \klcodebox{<lines>} — the block's lines, shaded when the block has a
|
||||
% comment (\klcomment is set by \klblock).
|
||||
%
|
||||
% The shading is ONE \colorbox around the whole block, not one per line,
|
||||
% because only a box around the whole block can have a vertical margin.
|
||||
%
|
||||
% That margin is \vspace* INSIDE the \parbox rather than \fboxsep: \fboxsep
|
||||
% pads all four sides, and any horizontal padding would put the box's left
|
||||
% edge out to the LEFT of the code column. With \fboxsep 0 the box spans
|
||||
% exactly the code's own extent, so it begins where the line's indentation
|
||||
% begins and the code stays aligned with the unshaded lines around it.
|
||||
\newcommand{\klcodebox}[1]{%
|
||||
\ifx\klcomment\empty
|
||||
\parbox[c]{\klcodewidth}{#1}%
|
||||
\else
|
||||
\setlength{\fboxsep}{0pt}%
|
||||
\colorbox{klcodeshade}{%
|
||||
\parbox[c]{\klcodewidth}{\vspace*{\klboxpad}#1\vspace*{\klboxpad}}}%
|
||||
\fi}
|
||||
|
||||
% \klblock{<widest line>}{<lines>}{<comment>}
|
||||
% <lines> is \klline/\klshaded calls separated by \\; <comment> may be
|
||||
% empty. The comment gets what the box leaves of \linewidth (NOT
|
||||
% \textwidth: inside a list or minipage they differ, and \textwidth would
|
||||
% push the comment into the margin). If that remainder is too narrow to
|
||||
% set prose in, the comment goes BELOW the block rather than being
|
||||
% squeezed into an overfull box.
|
||||
\newcommand{\klblock}[3]{%
|
||||
\begingroup
|
||||
% A block is boxed exactly when it has a comment, so this one test also
|
||||
% decides whether the block gets the \klboxgap separation.
|
||||
\def\klcomment{#3}%
|
||||
\ifx\klcomment\empty\else\vskip\klboxgap\fi
|
||||
\settowidth{\klcodewidth}{\ttfamily #1}%
|
||||
\addtolength{\klcodewidth}{2\klcodepad}%
|
||||
% The box advances the line by exactly \klcodewidth (\fboxsep is 0 in
|
||||
% \klcodebox). If a horizontal padding is ever reintroduced there, its
|
||||
% width must be subtracted here too: leaving it out overfills the line,
|
||||
% and \raggedright then breaks it -- dropping the comment onto the next
|
||||
% line at the margin, which looks like the narrow-comment fallback.
|
||||
\setlength{\klcommentwidth}%
|
||||
{\dimexpr\linewidth-\klindent-\klcodewidth-\klcodegap\relax}%
|
||||
\noindent
|
||||
\ifdim\klcommentwidth<\klcodemin
|
||||
\klcodebox{#2}%
|
||||
\ifx\klcomment\empty\else
|
||||
\\\parbox[t]{\linewidth}{\klcommentfont #3}%
|
||||
\fi
|
||||
\else
|
||||
\klcodebox{#2}%
|
||||
\ifx\klcomment\empty\else
|
||||
\hspace{\klcodegap}%
|
||||
\parbox[c]{\klcommentwidth}{\klcommentfont #3}%
|
||||
\fi
|
||||
\fi
|
||||
% End the block's line. Without this every block joins ONE horizontal
|
||||
% list and the blocks are broken into lines and justified like words.
|
||||
\par
|
||||
\ifx\klcomment\empty\else\vskip\klboxgap\fi
|
||||
\endgroup}
|
||||
|
||||
% The annotation font. Ragged right: a justified annotation beside a
|
||||
% narrow box hyphenates badly.
|
||||
\newcommand{\klcommentfont}{\sffamily\itshape\small\raggedright}
|
||||
|
||||
% \begin{klcode} ... \end{klcode} — the listing itself. \offinterlineskip
|
||||
% is what makes the struts the only thing setting vertical rhythm.
|
||||
\newenvironment{klcode}
|
||||
{\par\addvspace{0.5\baselineskip}%
|
||||
\begingroup
|
||||
\setlength{\parindent}{0pt}%
|
||||
\setlength{\parskip}{0pt}%
|
||||
\raggedright
|
||||
% An unboxed listing is shifted with \leftskip rather than wrapped in a
|
||||
% minipage, so that it can still break across pages.
|
||||
\leftskip=\klshift
|
||||
\offinterlineskip}
|
||||
{\endgroup\par\addvspace{0.5\baselineskip}}
|
||||
|
||||
@@ -17,51 +17,45 @@
|
||||
# @@image.k basename : Image read from a file @@
|
||||
# @@image.html basename | width | height : @eval import image ; result = image.image(K) eval@ @@
|
||||
|
||||
@@@argtype image_hpos |
|
||||
horizontal position of an image: the element positions (center, left,
|
||||
right, or a length used as the left margin), or none for an inline image
|
||||
with no positioning container
|
||||
:pattern 'element_hpos'^|none
|
||||
:default center
|
||||
@@@
|
||||
# An image once had its own position type, for the sake of the "none" value
|
||||
# (no positioning container). That value is meaningful for any block
|
||||
# element, so the shared hpos type carries it and an image is placed by the
|
||||
# hpos_args option set like every other block element.
|
||||
|
||||
@@image
|
||||
@@image.k
|
||||
basename
|
||||
:id
|
||||
:width.length .5w
|
||||
@caption_arguments@
|
||||
@caption_args@
|
||||
:vmargin.bool true
|
||||
:hpos.image_hpos
|
||||
@hpos_args@
|
||||
:rel
|
||||
:abswidth.number 0.0
|
||||
:border.bool false
|
||||
:
|
||||
@eval image.Image(K) eval@
|
||||
An image read from a file. The basename names the file, which is looked
|
||||
for in the image search path and converted to a format the target can use.
|
||||
^:width is the image's width in the text column, ^:hpos and ^:offset place
|
||||
it there, and the caption arguments caption it.
|
||||
@@
|
||||
|
||||
@@image_grid
|
||||
@@image :: @eval image.Image(K) eval@ @@
|
||||
|
||||
@@image_grid.k
|
||||
image_specs.rest(2)
|
||||
:caption
|
||||
:number.bool true
|
||||
:cell_number.bool false
|
||||
:landscape.bool false
|
||||
:scale.number 0.98
|
||||
:caption_side.caption_side
|
||||
:caption_side_center.bool true
|
||||
#
|
||||
@caption_args@
|
||||
@hpos_args@
|
||||
:thumbnail.bool false
|
||||
:allow_break.bool true
|
||||
# :indent.length
|
||||
# :xmargin.length
|
||||
:id
|
||||
:captionfont
|
||||
:caption_width.float .9
|
||||
:rel
|
||||
:hsep.number 0.02
|
||||
:
|
||||
@eval image_grid.Image_grid(K) eval@
|
||||
: A grid of images
|
||||
@@
|
||||
|
||||
@@fig spec.figure_id :
|
||||
@reference *spec* | Figure @
|
||||
@@
|
||||
@@image_grid :: @eval image_grid.Image_grid(K) eval@ @@
|
||||
|
||||
@@fig spec.figure_id : @reference *spec* | Figure @ @@
|
||||
|
||||
@@ -103,7 +103,8 @@ class Image(klammer_base.Klammer_base):
|
||||
|
||||
result = html_util.add_caption(
|
||||
result, "Figure", self.number, self.caption, self.caption_font,
|
||||
self.hpos, self.caption_side, False, self.caption_font_size)
|
||||
self.hpos, self.caption_side, False, self.caption_font_size,
|
||||
offset=self.offset)
|
||||
|
||||
result = E("div").body(result).cls("image_margin")
|
||||
|
||||
@@ -131,16 +132,19 @@ class Image(klammer_base.Klammer_base):
|
||||
self.caption += self.file_error_message
|
||||
result = latex_util.add_caption(
|
||||
result, "Figure", self.number, self.caption, width,
|
||||
self.hpos, self.caption_side)
|
||||
self.hpos, self.caption_side, offset=self.offset)
|
||||
#result, "Figure", self.number, self.caption, self.hpos, self.caption_side,
|
||||
#self.width, self.caption_side_center, self.vmargin, self.caption_margin)
|
||||
else:
|
||||
result = f'\\includegraphics[width={width}]{{{source}}}'
|
||||
result = latex_util.caption_wrapper(result, self.hpos)
|
||||
result = latex_util.caption_wrapper(result, self.hpos, offset=self.offset)
|
||||
name = f"Reference-Figure-{Image.id}"
|
||||
result = f"\\hypertarget{{{name}}}{{}}\\label{{Label-{name}}}\n{result}"
|
||||
Image.id += 1
|
||||
return result
|
||||
# \includegraphics (bare or inside a caption wrapper's minipage) is
|
||||
# box material: it must sit in vertical mode or it is typeset beside
|
||||
# any text it follows.
|
||||
return latex_util.block(result)
|
||||
|
||||
|
||||
def txt(self):
|
||||
|
||||
@@ -20,7 +20,13 @@ class Kargs:
|
||||
self.caption_side_center = False
|
||||
self.Image_search_path = K.Image_search_path
|
||||
self.number = K.cell_number
|
||||
# A cell is placed by the grid, not by itself: no positioning
|
||||
# container, and nothing to inset it from. Both members of the
|
||||
# hpos_args option set have to be set here -- image.py reads them
|
||||
# unconditionally, so a missing one is an AttributeError at render
|
||||
# time rather than a message.
|
||||
self.hpos = "none"
|
||||
self.offset = "0pt"
|
||||
self.K_target = target
|
||||
self.K_input_dir = K.K_input_dir
|
||||
self.K_output_dir = K.K_output_dir
|
||||
|
||||
@@ -1,22 +1,6 @@
|
||||
@@show s : @eval :cpp show show @ @@
|
||||
# General definitions for the Standard Klammer Set
|
||||
|
||||
@@@argtype caption_side |
|
||||
the side of its element on which a caption is placed
|
||||
:pattern top^|right^|bottom^|left
|
||||
:default bottom
|
||||
@@@
|
||||
|
||||
@@caption_arguments :
|
||||
:caption
|
||||
:number.bool true
|
||||
:caption_side.caption_side
|
||||
:caption_font.font i
|
||||
:caption_font_size.float .9
|
||||
@@
|
||||
|
||||
@@reference spec | name :
|
||||
__REF__*spec*__*name*__
|
||||
@@
|
||||
# Argtypes
|
||||
|
||||
@@@argtype number | a number
|
||||
:pattern 'float'^|'int'
|
||||
@@ -24,9 +8,9 @@
|
||||
@@@
|
||||
|
||||
@@@argtype length | a length specifier
|
||||
#:pattern f^|none^|'float'w^|'float'h^|'int'px^|'float'em^|'int'pt
|
||||
# The last pattern is a string, used where possible to determine its length
|
||||
:pattern f^|none^|'float'w^|'float'h^|'int'px^|'float'em^|'int'pt^|"[^^"]+"^|'float'pw^|'float'ph
|
||||
#:pattern f^|none^|'float'w^|'float'h^|'int'px^|'float'em^|'int'pt
|
||||
# The last pattern is a string, used where possible to determine its length
|
||||
:pattern f^|none^|'float'w^|'float'h^|'int'px^|'float'em^|'int'pt^|"[^^"]+"^|'float'pw^|'float'ph
|
||||
# :python_cast (lambda s : __import__("kutil").parse_length("tex", s))
|
||||
@@@
|
||||
|
||||
@@ -35,16 +19,49 @@
|
||||
#:python_cast (lambda s : [__import__("kutil").parse_length("tex", e) for e in s.split()])
|
||||
@@@
|
||||
|
||||
@@@argtype element_hpos |
|
||||
the horizontal position of a block element (a table or an image) within
|
||||
the text column: center, left, right, or a length, which places the
|
||||
element's left edge that far from the left margin (e.g. ^:hpos 4em, ^:hpos
|
||||
.25w). When the element is as wide as the text column, the positions are
|
||||
indistinguishable.
|
||||
:pattern center^|left^|right^|'length'
|
||||
@@@argtype side |
|
||||
the side of its element on which a caption is placed: top, right, bottom, left
|
||||
:pattern top^|right^|bottom^|left
|
||||
:default bottom
|
||||
@@@
|
||||
|
||||
@@@argtype hpos |
|
||||
a horizontal position: left, center, right, none
|
||||
:pattern left^|center^|right^|none
|
||||
:default center
|
||||
@@@
|
||||
|
||||
@@@argtype offset_length |
|
||||
how far a block element is inset from the margin that ^:hpos names: from
|
||||
the left margin for ^:hpos left, from the right margin for ^:hpos right.
|
||||
An offset has no meaning for a centered element and is ignored there.
|
||||
Written alone, ^:offset is the standard indentation (e.g. ^:hpos left
|
||||
^:offset), and a value overrides it (^:hpos right ^:offset 4em).
|
||||
# A single-purpose type, not a use of "length", because the two values
|
||||
# below are what the writer relies on and only a type can carry them: an
|
||||
# element is flush unless an offset is asked for (:default), and asking
|
||||
# without saying how much is the standard indentation (:alone). A general
|
||||
# length type must not declare :alone -- a bare option name has to read the
|
||||
# same way wherever it appears, and "2em" is meaningless for :width.
|
||||
:pattern 'length'
|
||||
:default 0pt
|
||||
:alone 2em
|
||||
@@@
|
||||
|
||||
|
||||
#[
|
||||
@@@argtype element_hpos |
|
||||
the horizontal position of a block element (a table, an image, a code
|
||||
listing) within the text column: center, left, right, or a length, which
|
||||
places the element's left edge that far from the left margin (e.g. ^:hpos
|
||||
4em, ^:hpos .25w). When the element is as wide as the text column, the
|
||||
positions are indistinguishable. The value none puts the element in no
|
||||
positioning container at all, so that it flows with the text around it.
|
||||
:pattern center^|left^|right^|none^|'length'
|
||||
:default center
|
||||
@@@
|
||||
]#
|
||||
|
||||
@@@argtype figure_id |
|
||||
an identifier for a figure.
|
||||
|
||||
@@ -89,8 +106,75 @@ An <id> is the value of the ^:id argument for an image.
|
||||
:pattern [a-z][a-z]
|
||||
@@@
|
||||
|
||||
# The document-wide language for generated text. A klammer's own :lang
|
||||
# argument overrides it; see the language argtype above. Consumers today:
|
||||
# @date and @datetime (month names and date form). Set it for a whole
|
||||
# document with @@@state Language :value de @@@
|
||||
@@@state Language :desc Language (ISO 639-1) for generated text :value en @@@
|
||||
|
||||
# State variables
|
||||
|
||||
@@@state Language :desc Language (ISO 639-1) for generated text.
|
||||
The document-wide language for generated text. A klammer's own ^:lang
|
||||
argument overrides it; see the language argtype above. Currently used by
|
||||
^@date and ^@datetime (month names and date form). Set it for a whole
|
||||
document with ^@^@^@state Language ^:value de ^@^@^@
|
||||
:value en
|
||||
@@@
|
||||
|
||||
|
||||
# Option sets
|
||||
|
||||
#[
|
||||
The horizontal placement of a block element is two parameters: where it sits
|
||||
(hpos_arg) and how far it is inset from that side (offset_arg). They are two
|
||||
sets rather than one because a klammer may need the second without the first
|
||||
-- and because a set is a vocabulary a reader learns whole, so a small one is
|
||||
easier to learn than a large one.
|
||||
|
||||
An option set is a claim about behavior, not just a saving of keystrokes: a
|
||||
klammer whose declaration uses these must also POSITION itself with them,
|
||||
through latex_util.caption_wrapper (tex) and html_util.hpos_container
|
||||
(html), or the parameter is accepted and silently ignored. Asserted by
|
||||
outcome in sks/tst/placement_test.sh, so a new block klammer that takes the
|
||||
parameters and ignores them is caught.
|
||||
|
||||
A klammer that wants a different DEFAULT position says so where it uses the
|
||||
set (@hpos_arg :hpos left @ in the code klammer): a set owns the names and
|
||||
the types, and the klammer owns what silence means for it.
|
||||
]#
|
||||
|
||||
#[
|
||||
@@offset_arg.o
|
||||
:offset.offset_length
|
||||
: How far a block element is inset from the margin its position names @@
|
||||
|
||||
@@hpos_arg.o
|
||||
:hpos.element_hpos
|
||||
: Where a block element sits in the text column @@
|
||||
]#
|
||||
|
||||
@@hpos_args.o
|
||||
:hpos.hpos
|
||||
:offset.offset_length
|
||||
:
|
||||
The horizontal position of a block element. If the ^:hpos value is "none", no
|
||||
outer structure is added to the element so that it can be used inline or in
|
||||
other structures. The ^:offset is used only if the ^:hpos value is "left" or
|
||||
"right".
|
||||
@@
|
||||
|
||||
@@caption_args.o
|
||||
:caption
|
||||
:number.bool true
|
||||
:caption_side.side
|
||||
:caption_font.font i
|
||||
:caption_font_size.float .9
|
||||
: Arguments that define a caption for a block element.
|
||||
@@
|
||||
|
||||
|
||||
# Klammers
|
||||
|
||||
@@reference.k spec | name : Reference marker for captioned elements @@
|
||||
@@reference ::
|
||||
__REF__*spec*__*name*__
|
||||
@@
|
||||
|
||||
@@show.k s : Show the raw Klammertext and the result @@
|
||||
@@show :: @eval :cpp show show @ @@
|
||||
|
||||
@@ -193,10 +193,10 @@
|
||||
|
||||
@@rowcolor.tex s : \colorrow{*s*} @@
|
||||
|
||||
@@table rows.rest(2)
|
||||
@@table.k rows.rest(2)
|
||||
:id
|
||||
@caption_arguments@
|
||||
:hpos.element_hpos
|
||||
@caption_args@
|
||||
@hpos_args@
|
||||
:header.bool true
|
||||
:allow_break.bool false
|
||||
:column_width.column_width
|
||||
@@ -215,9 +215,20 @@
|
||||
:leading.float 1.3
|
||||
:colsep 4pt
|
||||
:
|
||||
@eval table.Table(K) eval@
|
||||
A table of rows of cells. The cells of a row are separated by "|" and the
|
||||
rows by "||", so the argument is two-dimensional; the first row is the
|
||||
header unless ^:header is false.
|
||||
|
||||
Everything else is optional, and a table written with no options is a plain
|
||||
grid of its cells. ^:column_width lays the columns out, ^:hline and ^:vline
|
||||
draw lines, ^:colspan and ^:rowspan merge cells, ^:justify and ^:cell_hpos
|
||||
place text within them, ^:calc computes cells from other cells and ^:format
|
||||
formats them, and ^:hpos and ^:offset place the whole table in the text
|
||||
column.
|
||||
@@
|
||||
|
||||
@@table :: @eval table.Table(K) eval@ @@
|
||||
|
||||
@@tbl spec.figure_id :
|
||||
@reference *spec* | Table @
|
||||
@@
|
||||
|
||||
@@ -591,10 +591,11 @@ class Table(klammer_base.Klammer_base):
|
||||
result, "Table", self.number, self.caption,
|
||||
self.caption_font, hpos=self.hpos,
|
||||
side=self.caption_side,
|
||||
font_size=self.caption_font_size, max_width="100%")
|
||||
font_size=self.caption_font_size, max_width="100%",
|
||||
offset=self.offset)
|
||||
else:
|
||||
result.attr("style", "max-width: 100%")
|
||||
result = html_util.hpos_container(result, self.hpos).str()
|
||||
result = html_util.hpos_container(result, self.hpos, self.offset).str()
|
||||
return result
|
||||
colgroup, layout, table_width = self.html_colgroup()
|
||||
result = E("table").body(colgroup + result)
|
||||
@@ -607,13 +608,14 @@ class Table(klammer_base.Klammer_base):
|
||||
result = html_util.add_caption(
|
||||
result, "Table", self.number, self.caption, self.caption_font,
|
||||
hpos=self.hpos, side=self.caption_side,
|
||||
font_size=self.caption_font_size, width=table_width)
|
||||
font_size=self.caption_font_size, width=table_width,
|
||||
offset=self.offset)
|
||||
else:
|
||||
# An uncaptioned table still gets the position container, so
|
||||
# html and tex agree on where the table sits.
|
||||
if table_width:
|
||||
result.attr("style", f"{layout}width: {table_width}")
|
||||
result = html_util.hpos_container(result, self.hpos).str()
|
||||
result = html_util.hpos_container(result, self.hpos, self.offset).str()
|
||||
return result
|
||||
|
||||
# LaTeX
|
||||
@@ -768,14 +770,25 @@ class Table(klammer_base.Klammer_base):
|
||||
# by its :hpos wrapper instead; its glue is left neutral (\fill on
|
||||
# both sides collapses in the exactly-fitting box), because a fixed
|
||||
# length would overflow the box. A length is the left margin.
|
||||
# :offset insets the table from the margin :hpos names -- the same
|
||||
# rule the boxed path gets from latex_util.caption_wrapper, here
|
||||
# expressed as the fixed side of the glue pair.
|
||||
inset = latex_util.offset_length(self.offset)
|
||||
if not self.allow_break:
|
||||
left, right = "\\fill", "\\fill"
|
||||
elif self.hpos == "center":
|
||||
left, right = "\\fill", "\\fill"
|
||||
elif self.hpos == "left":
|
||||
left, right = "0pt", "\\fill"
|
||||
left, right = inset, "\\fill"
|
||||
elif self.hpos == "right":
|
||||
left, right = "\\fill", "0pt"
|
||||
left, right = "\\fill", inset
|
||||
elif self.hpos == "none":
|
||||
# No positioning container: the table starts at the text margin
|
||||
# like ordinary text, which is what "none" does in html (the
|
||||
# hpos_none class is inline-flex). A longtable cannot flow
|
||||
# inline, so flush left is as close as the target gets. The
|
||||
# offset names no margin here and is ignored, as when centered.
|
||||
left, right = "0pt", "\\fill"
|
||||
else:
|
||||
length, _, _ = kutil.parse_length("tex", self.hpos, 1)
|
||||
left, right = length, "\\fill"
|
||||
@@ -824,9 +837,9 @@ class Table(klammer_base.Klammer_base):
|
||||
result, "Table", self.number, self.caption, "\\tableboxwidth",
|
||||
hpos=self.hpos, side=self.caption_side,
|
||||
font_symbol=self.caption_font,
|
||||
font_size=self.caption_font_size)
|
||||
font_size=self.caption_font_size, offset=self.offset)
|
||||
else:
|
||||
result = latex_util.caption_wrapper(result, self.hpos)
|
||||
result = latex_util.caption_wrapper(result, self.hpos, offset=self.offset)
|
||||
result = measure + result
|
||||
|
||||
result = f"\\hypertarget{{{name}}}{{}}\\label{{Label-{name}}}\n{result}"
|
||||
@@ -845,7 +858,11 @@ class Table(klammer_base.Klammer_base):
|
||||
+ self.tex_fill_widths()
|
||||
+ result)
|
||||
result = re.sub(r"\newline", r"\\\\", result)
|
||||
return result
|
||||
# A boxed table is box material: it must sit in vertical mode or it
|
||||
# is typeset beside any text it follows. (A page-breaking longtable
|
||||
# breaks the paragraph itself, but \par on both sides is a no-op
|
||||
# there, so the rule stays unconditional.)
|
||||
return latex_util.block(result)
|
||||
|
||||
def txt(self):
|
||||
return "Table in .txt format not implemented"
|
||||
|
||||
@@ -168,6 +168,14 @@
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
/* Retired 2026-08-03: this fixed 1em on every left- and right-placed
|
||||
element was the :offset argument, hard coded and only in html -- the tex
|
||||
side had no counterpart, so the two targets disagreed. It is now the
|
||||
offset_length argtype (sks/kutil/kutil.k), applied by
|
||||
html_util.hpos_container and latex_util.caption_wrapper. A document
|
||||
that wants the old inset writes ":offset", whose alone value is the
|
||||
standard indentation.
|
||||
.hpos_margin {
|
||||
margin: 0 1em 0 1em;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -193,27 +193,38 @@ def element_tag(element):
|
||||
def font_class(font):
|
||||
return {"r" : "", "i" : "ritalic", "t" : "monospace", "s" : "sanserif"}[font]
|
||||
|
||||
def hpos_container(element, hpos):
|
||||
def hpos_container(element, hpos, offset=None):
|
||||
# Wrap element in its horizontal-position container. hpos is left,
|
||||
# center, right, none, or a length, which becomes the left margin
|
||||
# (the tex counterparts are the \LTleft glue for tables and the
|
||||
# \hspace* in latex_util.caption_wrapper).
|
||||
style = None
|
||||
#
|
||||
# offset insets the element from the margin hpos names, as padding on
|
||||
# that side of the flex container: the left for "left", the right for
|
||||
# "right". A centered element has no such margin, so the offset is
|
||||
# ignored there rather than being an error -- same rule as
|
||||
# latex_util.caption_wrapper, which is the tex counterpart. (It
|
||||
# replaces the fixed 1em that the hpos_margin class used to add to
|
||||
# every left- and right-placed element: that was this offset, hard
|
||||
# coded, and only in html.)
|
||||
styles = []
|
||||
if hpos not in ("left", "center", "right", "none"):
|
||||
length, _, _ = kutil.parse_length("html", hpos, 1)
|
||||
style = f"margin-left: {length}"
|
||||
styles.append(f"margin-left: {length}")
|
||||
hpos = "indent"
|
||||
if offset and offset not in ("0", "0pt") and hpos in ("left", "right", "indent"):
|
||||
length, _, _ = kutil.parse_length("html", offset, 1)
|
||||
side = "right" if hpos == "right" else "left"
|
||||
styles.append(f"padding-{side}: {length}")
|
||||
result = E("div").cls("hpos_" + hpos).body(element)
|
||||
if style:
|
||||
result.attr("style", style)
|
||||
if hpos not in ("center", "indent"):
|
||||
result.cls("hpos_margin")
|
||||
if styles:
|
||||
result.attr("style", "; ".join(styles))
|
||||
return result
|
||||
|
||||
|
||||
def add_caption(element, caption_label, number, caption_text,
|
||||
font_symbol="i", hpos="center", side="bottom", as_string=True,
|
||||
font_size=.9, width=None, max_width=None):
|
||||
font_size=.9, width=None, max_width=None, offset=None):
|
||||
tag = element_tag(element)
|
||||
# Caption
|
||||
caption = ""
|
||||
@@ -263,7 +274,7 @@ def add_caption(element, caption_label, number, caption_text,
|
||||
element.attr("style", f"max-width: {max_width}")
|
||||
|
||||
|
||||
result = hpos_container(element, hpos)
|
||||
result = hpos_container(element, hpos, offset)
|
||||
|
||||
if number:
|
||||
result.cls("element_container")
|
||||
|
||||
@@ -12,6 +12,31 @@ def tex_style():
|
||||
|
||||
# \usepackage{quoting}
|
||||
|
||||
def block(material):
|
||||
r"""Return tex material as a block: vertical mode on both sides.
|
||||
|
||||
A block element -- a table, an image, a code listing -- is box
|
||||
material. Appended to a non-empty horizontal list (an @image written
|
||||
after text on the same line, with no blank line between) a box is
|
||||
typeset BESIDE the text rather than below it, and text following it
|
||||
flows to its right. A \par on each side ends the paragraph in
|
||||
progress and starts a new one after. \par in vertical mode is a
|
||||
no-op, so this is safe wherever it is applied: a block klammer needs
|
||||
no knowledge of what preceded it.
|
||||
|
||||
Every SKS klammer whose tex output is box material returns it through
|
||||
this function. LaTeX ENVIRONMENTS -- center, flushright, quote,
|
||||
itemize, verbatim -- already break the paragraph themselves and do
|
||||
not need it.
|
||||
|
||||
The paragraph policy this serves: in an @document, paragraphs are
|
||||
made from blank-line-separated text (@par is the explicit form for a
|
||||
fragment rendered without @document), and the author is never
|
||||
required to know that a target distinguishes horizontal from
|
||||
vertical mode. Asserted by sks/tst/paragraph_test.sh.
|
||||
"""
|
||||
return "\\par\n" + material.strip("\n") + "\n\\par\n"
|
||||
|
||||
def environment(name, body, required=None, optional=None):
|
||||
req = f"{{{required}}}" if required else ""
|
||||
opt = f"[{optional}]" if optional else ""
|
||||
@@ -54,17 +79,36 @@ def minipage(content, width="\\textwidth", vertical="c", center=True, vmargin=""
|
||||
result = f"\\fbox{{{result}}}"
|
||||
return result
|
||||
|
||||
def caption_wrapper(element, hpos, bottom_margin=.67):
|
||||
def offset_length(offset):
|
||||
r"""An :offset as a LaTeX length, or "0pt" when there is none."""
|
||||
if not offset or offset in ("0", "0pt"):
|
||||
return "0pt"
|
||||
return kutil.parse_length("tex", offset, 1)[0]
|
||||
|
||||
def offset_space(offset):
|
||||
r"""An :offset as an \hspace*, or "" when there is none."""
|
||||
length = offset_length(offset)
|
||||
return "" if length == "0pt" else f"\\hspace*{{{length}}}"
|
||||
|
||||
def caption_wrapper(element, hpos, bottom_margin=.67, offset=None):
|
||||
# hpos is left, center, right, none (no wrapper), or a length, which
|
||||
# becomes the left margin. The element is a box on a line inside a
|
||||
# full-width minipage; \hfill on the empty side pushes it into place.
|
||||
#
|
||||
# offset insets the element from the margin hpos names -- from the left
|
||||
# for "left", from the right for "right". A centered element has no
|
||||
# such margin, so the offset is ignored there rather than being an
|
||||
# error: the two arguments are independent, and :hpos is what decides
|
||||
# whether the offset has anything to measure from. (The html
|
||||
# counterpart is html_util.hpos_container.)
|
||||
vmargin = f"{bottom_margin}\\baselineskip"
|
||||
inset = offset_space(offset)
|
||||
if hpos == "center":
|
||||
return minipage(element, vmargin=vmargin)
|
||||
if hpos == "left":
|
||||
return minipage(element + "\\hfill", vmargin=vmargin, center=False)
|
||||
return minipage(inset + element + "\\hfill", vmargin=vmargin, center=False)
|
||||
if hpos == "right":
|
||||
return minipage("\\hfill" + element, vmargin=vmargin, center=False)
|
||||
return minipage("\\hfill" + element + inset, vmargin=vmargin, center=False)
|
||||
if hpos == "none":
|
||||
return element
|
||||
length, _, _ = kutil.parse_length("tex", hpos, 1)
|
||||
@@ -85,7 +129,8 @@ def make_caption_text(number, label, text, font_symbol, font_size):
|
||||
return caption
|
||||
|
||||
def add_caption(element, caption_label, number, caption_text, latex_width,
|
||||
hpos="center", side="bottom", font_symbol="i", font_size=.9):
|
||||
hpos="center", side="bottom", font_symbol="i", font_size=.9,
|
||||
offset=None):
|
||||
top_margin = .75 if "includegraphics" in element else .5
|
||||
caption = make_caption_text(number, caption_label, caption_text, font_symbol, font_size)
|
||||
if caption:
|
||||
@@ -118,4 +163,4 @@ def add_caption(element, caption_label, number, caption_text, latex_width,
|
||||
# space below its line.
|
||||
element = minipage(caption + "\\rule[-0.75\\baselineskip]{0pt}{0pt}\n" + element,
|
||||
latex_width, vertical="t")
|
||||
return caption_wrapper(element, hpos)
|
||||
return caption_wrapper(element, hpos, offset=offset)
|
||||
|
||||
Reference in New Issue
Block a user