From db16d494528b35ee79d39f3f9d7ffcd47cd3df57 Mon Sep 17 00:00:00 2001
From: Jan Rydzewski
Date: Wed, 10 Jan 2024 10:40:39 +0100
Subject: [PATCH 1/3] Add Strong inline element
---
example.docx | Bin 846064 -> 846165 bytes
example.html | 9 ++++++++-
example.pdf | Bin 6839 -> 6943 bytes
example.py | 5 +++--
red_tape_kit/ast.py | 15 +++++++++++++++
red_tape_kit/html.py | 7 +++++--
red_tape_kit/pdf.py | 12 +++++++++---
7 files changed, 40 insertions(+), 8 deletions(-)
diff --git a/example.docx b/example.docx
index 8b60bced19cb88c19f8ae98fb52a5b80e559ce38..0208183c4e0e37a556d6963984dd4bed1edbee64 100644
GIT binary patch
delta 394
zcmYMvOG`pQ6bJA-zO7RPE`(B=ooNp%p@nFXASSi&D_pMEoGh?_dvvNILZzbmdTlN1RcvOcNImB8WqNI#t4G0+ePmMz9#5{<54e*dlJa51(
zdBRQu=1B#on=nUes5T*Fcs>T9R9vv(V-q}ja~bfq1jA@=@!krCS`gOh5D!|se~r}^
z1j#3w+7Q%DRWRI!K;P&M2zf<~dlQPHVXO^ye7}bQ5dLO|j6dLA8{DSmM>qTi+YyTY
delta 246
zcmcb5$@s%X;|(8#IonUY;1@GsU=Z8rcmf#Jr@KGp@nqDWKJh7!4b#d6)6YNU
zapMXJ;9)4wFG|rbo}T!OM|!%|Gaf0%tm&c8cpMpnrcZyyW6xZh&NKagI*-`&_s@8=
z7_+BKJ?C*?S~Pij%yS+uM%L-epYymf&7C&=-E$s0#<1zSFL-PjCr-}>QcP#IO<(l_
bXxXtn(_et-6Njd&z2wnkYkbVZz`y_iW+Y$?
diff --git a/example.html b/example.html
index 04f48de..1697c8a 100644
--- a/example.html
+++ b/example.html
@@ -29,7 +29,14 @@
The first paragraph.
- The second paragraph.
+
+ Kava
+
+ or
+
+ kava kava
+
+ (Piper methysticum: Latin 'pepper' and Latinized Greek 'intoxicating') is a crop of the Pacific Islands.
We allow
diff --git a/example.pdf b/example.pdf
index 79cf9de1addc2088023afd9c2bd2a3f75bf9b3f4..463919079039d81d7f555d9135a6c5d30d00992a 100644
GIT binary patch
delta 2918
zcmai0c|6qVAK%==FvPg82;+#k57)4AwM4lWP1LwH4AvTwugVcxVkkmVD`8!cVN56n
zu{$CmgB&?FqBZ3PA&SX8{}nBW!9a9Pwi~*(X?-T9A`tAIgD)la}|O%o2XX&h1C=95CiH;8y
zoAJIMNSlWJJk)``K=$5T)G`XWpjA1eXrCF|;da!1ij$fA?X$)HS)n94pO9tW)qbfE
zD+QWceiDQdXUc44Gz%=?Z_6Pu_I%_I<-{wiNtrrML}p;X%UT
zt9RwrreQ0OWGoxao_YRSIwMGX2bLaXyEb9^!@~rK*%$=)bRpyZp@-B**gPPHhlHvm
zojvOdn4K)lyZddso&M0L<=F
zhAhs_ElpL6?DI&PMDDp|i$>5mEss$dMMx?&tZZsQx~VKG^_oRdk=qk$mlLS(eBll0frA?mAC(?QLmsEhfKHVKzoJ$F
zJ3)5W%yj5zeN@3SYM@WCD||>?Yv=?2Y|E{F@PM6X^QetM>_-dO=IPpa#AMrdUUTv-
z5w8U5g|tUj!x2LsT_qqW`8cTf4$1VXsc`WjBxp+DzNTcI|F*2|{1jaL95a_8U+>PY
zw4I+#{m|hOiD4~z-W+=|Hn7Q%Jz{-ETCtnbs(oS<5bZ
zIvY5lIx9|AHqJx2f`~?rQ;0QP*8BS~;&E-U^NsJ-0#JHHaeYRYGVZX9y#{9akOYp$%>OIf0VB<^G
zt9p8^!AvAqlUcVxT8VkHeiI&ic2ju1x)f+!-UCrHSpP_(OJ=KCrawRxq3haMI1l_v
znKc<4kx7L>eRh(!s(yXvXF`c=5F_?LC3f^DlVLPD>Cv6dHeK(e2qqCc-niM
zV$(9ksVXfv`f->CA*zw?e~SdTL~g%zUD8^*IHjZ5%v!R|F@<90{+#3NwU4*Yu*gn!
z5BuCpTWxyfu^x9`k-wLj)sWI~4te77#C#p*T}_W(hd%g2W-v4#yu#R>5)X?odlVbJ
z?z)l^oXDw2Gb4?*aOI4odMgEHB%lz(dUv?4Rn6m{e;^WXUKz<(~)|6l(;Bjb9Jo
z8>VV&^4>|nx428UOWGe*pg^aao$!7IZ55iU*IynT_$V->Ti9vWiuPoN8H}p_2@H|PRg*ga{i5&
zWbu3K{*42_?xA+d*k+P01FO#_OLxZLy~Kd!noY
zu6xl7lNowzQXP?@qx)2?Yi_hYkxl@O`^Q5Vne!yb%N%TVk;uxnm5BaG4B`ci1aI<#
z8$_<26G~#n)lc&A7Y9wUP4spO9T@dXq1cDjZ*U}o#Z4pqv6?N0adqgeMRiffGfQIG
z4l2W?21UcOvoiy7vGXNf8H=;yJ1-a2^r~Z5-nAL7>QL%|mBHwr91EzfBkt5CW5Zkr
zW1YJ)XXHbtDAdVgQn@14g?+ozc1+!FZD!;d$Xz?8POj91QJ)}n%|Gu=8TaJ;YWzrt
zf1FQZM&}QTHP4HZ4oL${qh-O^wbBqXmdX}4a88Csbm7(JHf}i(&USUwUACIKxG%*U
zuvQu{$x;EfxVC)VZyk`WurCbbjORt?2~n$EPk^lCmmTvINodQ~XD8}Unqo*5H)cN}
zT-Tm^G}*JFb%OX>fwI;`7<1jVT~;YFSvFwrzT;+15F5N6(TMEurD=zleRu50+l+|D
z+E26zB72wp#culB?%cB^j70(tpS4PS1-qxj=ulu_$2!T3M(o*TZZGbso8Ne@LXJDN
zx51T|Vz$DPo0kmA_L}2P_W>m5o<1^HW}ub0J+n8==}Xq2?}zX6%Is8)*SUy61bC1L
znx9k+4+=?>g;IIoNLm&&hX;YCX-lUa!qz>OPCE4Y2c!!BGE_rUh5s_(NH`MnZw!S%
zp$=fkgIW}ha9}+Y0g3##7X3NOUl{OJU^D`GAP5@yUl;*@U_mteKQR1341*_ptp&c+
zjK&_!8jbrW@h={p@ORcjGz
Y_=NvR4JZ2vV-aYAu!e@EgO%|A02E;ECjbBd
delta 2826
zcmai#cTiL58i#=hNT@2+rLR(?29lFR5TpuHFN*ZgM3E*znos(VvzO^rUMbRrUgPc%O9TU9EG{Jy&QS9&RiQ}>fcvmwscvk#dZ>ko-g9K%J%!;
ze*7LgB}>@|-1sgm>;jo#tiUxrMVx$|aOgpt=>f&=lLvA$4ZJ|&Y`k3enPMqP%)Q}W
zw`;s>psHFPnGq^d$vaZ>>(i=;5W6t;+=W)l61WitD!$Jy{y>^ri>!D!F0*1EsH|&!
zv)xQJ(TX<`n2?_$)ve83)^kL5f^Q^qPk}=V|vBU7nv&JFi@I-c>+`rFmyRrNH?yl!#|5QQQqKCn2I|
z)z#n3ht+4UCYK9HI5!i@9W|?Eil2)u09MVTeCG=WHV0Y$Hwut`%2_ZBrcETM-X^5Y
zZf3ZcdmEf;;miGOi);wtjm@OUD?e1#dl#=Zum6(k`ss5<(Rk_C?U!BGW!9PDDllfC
zd+&_@um;9n-oZb7us^1yPkJ?LfJ+_rW0~5)7P9neRmZK>BSf&SC^APiQOQArwIPT9
zEe=quy;YSDXm8QcTJ+YD(?xTvbHxSQ5-6u93h-#Hqqa(wjE2MpIjuIYbT_9EyY1*k
z8n_$vus?wfVW3Z%u#R`WHM9{Pmf3yH;;(+>!^EH9e+{Bhz~4XI0uKkOl~~&JmPDhD
z#t%Qw?*t$UB_P+~8aZ$cHj47pH%iq_!lrYIRL>ZR*Ts9t3^h)4KJt
z_5<|QvI?@#hQn@e9h4fB9jRBmFyzfVA=j&QfbWJ(+AoHT+QKx
zMo%60PWR2L8@cZtJ;sUN+IBhD#=ajmJ7#~-H_sWH9^Q11C{huwwV!sXD!f*}+29yc
zO7r;Xfbc-4tFb4>Vb+)UWgmdbm5P>26|xJrU*}3k)j)e~-^k^g$$fhCld8q`kK)u6
zYH3F7NNxa8EcE$-U8xjQZrk^RuD9E}<_E6uuGMH48dKN}b(liubHruV!b*(v1)z#t
z-80v|Q|~Kf_GCmtNWJj17{u^Hnu=(pl5BkHe8a>gDYGLZtbNx`nfmiL)fQ
zPmrPE6W|`D;ki##cq45FG_xDupwvDtYVr_`_t2!eJW%nQNL%hmupD}L+a*Axx`tG`
z5JDH~2#Loq)4ylP)hz@x*6Ez&07t4ZRWS8l%
z<*=nCGO5+iFQJ={WmxUuDiQGNH#d(ln&a5;=4{Xx=;XbSX3tyTq}^M(;M?DwxnqNN
zS28!zif+d3d>W}QTDm_sI@geUx9gjR=8S#Z%J`2mT?}`&%9CoUZ=3U3UBrzFK<*L9*-zM<$T@Kax$$HRSp~!SAfO5PSF)2GJ;3-qiL$$p@RU)0~6=0kd*=Pejcv{t+DCx}pu994C=J9F_c`Tu2h|TnFPsT
zT*a5sKVP`My`8YO^-Eai3{g1l@xbVIM04T@*X-=l$t}8X!sa^Gt8`ksq&o)FPbXle
z+KoCi%UAJ8jGU_~hBsFdEHxmDWE~McW(-@>r+&|%5c=5!$q&4nRrrTUhRp&=Td9k9+L7hcoa_g7F{2{B4e3N<<{AsKt
z2li{>icRX2@etE*^-+*wj{<|O&cAp&78q8GU%u?=TruCfER|7|b
zZQ(U(b?j@qM?@dt$P|@hd-D0ji>s>Wcb+UdTVVEFA=6sqnIt%}qZ$XXXQMJEvW(|h
z)Os4L=Z^QJ_sC9Pflc&kpBu@GX}zp4z*oe?iXgx0eRrnse4VBKoC%$38LdlZI`gc1
z*OTzZ{gkGK$BaCoWEJqIt*i9kcTS}!?onp>b~UTj2uGXDw%U3(3o5Y$6F?Syt&eqc
z0&L#pj<)-`r@mc8l#fRrz>~zE@W7GyB#Bra7!toNk 'InlineElement':
def plain_string(self) -> str:
raise NotImplementedError
+ def __add__(self, other: 'InlineElement') -> 'InlineSequence':
+ return InlineSequence(items=[self, other])
+
@dataclass
class Document:
@@ -263,6 +266,18 @@ def plain_string(self) -> str:
return self.text.plain_string
+@dataclass(frozen=True)
+class Strong(InlineElement):
+ text: InlineElement
+
+ def normalized(self) -> 'Strong':
+ return Strong(text=normalized_inline(self.text))
+
+ @property
+ def plain_string(self) -> str:
+ return self.text.plain_string
+
+
def normalized_inline(element: InlineElement) -> InlineElement:
if isinstance(element, str):
return Text(text=element).normalized()
diff --git a/red_tape_kit/html.py b/red_tape_kit/html.py
index a87f05a..718e916 100644
--- a/red_tape_kit/html.py
+++ b/red_tape_kit/html.py
@@ -2,8 +2,8 @@
from base64 import b64encode
from .ast import (
- Attachment, DefinitionList, Image, InlineSequence, Paragraph, Section, Sequence, Table, TableCellSpan, Text,
- UnorderedList,
+ Attachment, DefinitionList, Image, InlineSequence, Paragraph, Section, Sequence, Strong, Table, TableCellSpan,
+ Text, UnorderedList,
)
@@ -131,6 +131,9 @@ def add_inline_element(self, html_el, element):
self.add_inline_element(html_el, sub_element)
elif isinstance(element, Attachment):
self.add_attachment(html_el, element)
+ elif isinstance(element, Strong):
+ strong = ET.SubElement(html_el, 'strong')
+ self.add_inline_element(strong, element.text)
else:
raise ValueError(f'Unknown inline element type {type(element)}')
diff --git a/red_tape_kit/pdf.py b/red_tape_kit/pdf.py
index 6641f9e..021adc7 100644
--- a/red_tape_kit/pdf.py
+++ b/red_tape_kit/pdf.py
@@ -1,10 +1,10 @@
from logging import getLogger
-from fpdf import FPDF, TitleStyle, XPos, YPos
+from fpdf import FPDF, FontFace, TitleStyle, XPos, YPos
from .ast import (
- Attachment, DefinitionList, Image, InlineSequence, Paragraph, Section, Sequence, Table, TableCellSpan, Text,
- UnorderedList,
+ Attachment, DefinitionList, Image, InlineSequence, Paragraph, Section, Sequence, Strong, Table, TableCellSpan,
+ Text, UnorderedList,
)
@@ -184,6 +184,8 @@ def add_inline_element(self, inline_element):
return self.add_inline_sequence(inline_element)
elif isinstance(inline_element, Attachment):
return self.add_attachment(inline_element)
+ elif isinstance(inline_element, Strong):
+ return self.add_strong(inline_element)
else:
raise ValueError(f'Unknown inline element type {inline_element}')
@@ -216,6 +218,10 @@ def add_attachment(self, attachment):
)
return True
+ def add_strong(self, strong):
+ with self.use_font_face(FontFace(emphasis="BOLD")):
+ return self.add_inline_element(strong.text)
+
def header(self):
pass
From 0a8ab7c479dcbbed3df77b8405fd65a7165acd1c Mon Sep 17 00:00:00 2001
From: Jan Rydzewski
Date: Wed, 10 Jan 2024 11:06:43 +0100
Subject: [PATCH 2/3] Improve PDF sequence spacing
---
red_tape_kit/pdf.py | 14 +++++++++-----
tests/test_renderers.py | 27 +++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 5 deletions(-)
diff --git a/red_tape_kit/pdf.py b/red_tape_kit/pdf.py
index 021adc7..413fc3f 100644
--- a/red_tape_kit/pdf.py
+++ b/red_tape_kit/pdf.py
@@ -19,7 +19,10 @@ class FPDFRenderer(FPDF):
MARGIN_RIGHT = 20 # mm
MARGIN_TOP = 20 # mm
MARGIN_BOTTOM = 20 # mm
+ SEQUENCE_SPACE = 7 # mm
+ UNORDERED_LIST_HSPACE = 7 # mm
UNORDERED_LIST_BULLET = '-'
+ UNORDERED_LIST_BULLET_SPACE = 5 # mm
def __init__(self, document, **kwargs):
super().__init__(**kwargs, unit='mm', format='A4')
@@ -114,7 +117,7 @@ def add_sequence(self, sequence, level):
ln_required = False
for sub_element in sequence.items:
if ln_required:
- self.ln(7)
+ self.ln(self.SEQUENCE_SPACE)
ln_required = self.add_element(sub_element, level)
if ln_required:
anything_generated = True
@@ -125,7 +128,8 @@ def add_section(self, section, level):
return self.add_element(section.body, level + 1)
def add_paragraph(self, paragraph):
- return self.add_inline_element(paragraph.text)
+ self.add_inline_element(paragraph.text)
+ return True
def add_table(self, table_data):
with self.table(
@@ -150,11 +154,11 @@ def add_elementary_table(self, pdf_table, elementary_table):
def add_unordered_list(self, unordered_list):
orig_left_margin = self.l_margin
- new_left_margin = orig_left_margin + 5
+ new_left_margin = orig_left_margin + self.UNORDERED_LIST_BULLET_SPACE
self.set_left_margin(new_left_margin)
for i, item in enumerate(unordered_list.items):
if i > 0:
- self.ln(7)
+ self.ln(self.UNORDERED_LIST_HSPACE)
with self.unbreakable() as self:
self.set_x(orig_left_margin)
self.cell(text=self.UNORDERED_LIST_BULLET)
@@ -191,7 +195,7 @@ def add_inline_element(self, inline_element):
def add_text(self, text):
self.write_lh(text.text)
- return text != ''
+ return text.text != ''
def add_inline_sequence(self, inline_sequence):
anything_generated = False
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 42cabf6..4b60b9d 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -142,6 +142,33 @@ def test_not_dependent_on_current_date(render):
assert a == b
+@pytest.mark.xfail(reason='Preserving final ln in fpdf is hard')
+def test_empty_paragraph_at_the_end_is_represented(render):
+ doc_a = DocumentFactory(
+ body=Sequence([
+ Paragraph(text=Text('A')),
+ Paragraph(text=Text('')),
+ ]),
+ )
+ doc_b = DocumentFactory(
+ body=Paragraph(text=Text('A')),
+ )
+ assert render(doc_a) != render(doc_b)
+
+
+def test_empty_paragraph_is_represented(render):
+ doc_a = DocumentFactory(
+ body=Sequence([
+ Paragraph(text=Text('')),
+ Paragraph(text=Text('A')),
+ ]),
+ )
+ doc_b = DocumentFactory(
+ body=Paragraph(text=Text('A')),
+ )
+ assert render(doc_a) != render(doc_b)
+
+
def test_empty_attachment_no_smoke(render):
doc = DocumentFactory(
body=Paragraph(text=Attachment(
From c73f2825befd88b244b053f28f14f857ea4caad2 Mon Sep 17 00:00:00 2001
From: Jan Rydzewski
Date: Wed, 10 Jan 2024 11:07:11 +0100
Subject: [PATCH 3/3] Version 0.5.1
---
pyproject.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index 9ecb4d2..d7a86c9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "red-tape-kit"
-version = "0.5.0"
+version = "0.5.1"
description = ""
authors = ["Your Name "]
readme = "README.md"