init: 폴더구조 설계 및 인프라 설계
This commit is contained in:
@@ -0,0 +1,618 @@
|
||||
"""Custom element classes for text-related XML elements"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Callable, cast
|
||||
|
||||
from pptx.enum.lang import MSO_LANGUAGE_ID
|
||||
from pptx.enum.text import (
|
||||
MSO_AUTO_SIZE,
|
||||
MSO_TEXT_UNDERLINE_TYPE,
|
||||
MSO_VERTICAL_ANCHOR,
|
||||
PP_PARAGRAPH_ALIGNMENT,
|
||||
)
|
||||
from pptx.exc import InvalidXmlError
|
||||
from pptx.oxml import parse_xml
|
||||
from pptx.oxml.dml.fill import CT_GradientFillProperties
|
||||
from pptx.oxml.ns import nsdecls
|
||||
from pptx.oxml.simpletypes import (
|
||||
ST_Coordinate32,
|
||||
ST_TextFontScalePercentOrPercentString,
|
||||
ST_TextFontSize,
|
||||
ST_TextIndentLevelType,
|
||||
ST_TextSpacingPercentOrPercentString,
|
||||
ST_TextSpacingPoint,
|
||||
ST_TextTypeface,
|
||||
ST_TextWrappingType,
|
||||
XsdBoolean,
|
||||
)
|
||||
from pptx.oxml.xmlchemy import (
|
||||
BaseOxmlElement,
|
||||
Choice,
|
||||
OneAndOnlyOne,
|
||||
OneOrMore,
|
||||
OptionalAttribute,
|
||||
RequiredAttribute,
|
||||
ZeroOrMore,
|
||||
ZeroOrOne,
|
||||
ZeroOrOneChoice,
|
||||
)
|
||||
from pptx.util import Emu, Length
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pptx.oxml.action import CT_Hyperlink
|
||||
|
||||
|
||||
class CT_RegularTextRun(BaseOxmlElement):
|
||||
"""`a:r` custom element class"""
|
||||
|
||||
get_or_add_rPr: Callable[[], CT_TextCharacterProperties]
|
||||
|
||||
rPr: CT_TextCharacterProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:rPr", successors=("a:t",)
|
||||
)
|
||||
t: BaseOxmlElement = OneAndOnlyOne("a:t") # pyright: ignore[reportAssignmentType]
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
"""All text of (required) `a:t` child."""
|
||||
text = self.t.text
|
||||
# -- t.text is None when t element is empty, e.g. '<a:t/>' --
|
||||
return text or ""
|
||||
|
||||
@text.setter
|
||||
def text(self, value: str): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
self.t.text = self._escape_ctrl_chars(value)
|
||||
|
||||
@staticmethod
|
||||
def _escape_ctrl_chars(s: str) -> str:
|
||||
"""Return str after replacing each control character with a plain-text escape.
|
||||
|
||||
For example, a BEL character (x07) would appear as "_x0007_". Horizontal-tab
|
||||
(x09) and line-feed (x0A) are not escaped. All other characters in the range
|
||||
x00-x1F are escaped.
|
||||
"""
|
||||
return re.sub(r"([\x00-\x08\x0B-\x1F])", lambda match: "_x%04X_" % ord(match.group(1)), s)
|
||||
|
||||
|
||||
class CT_TextBody(BaseOxmlElement):
|
||||
"""`p:txBody` custom element class.
|
||||
|
||||
Also used for `c:txPr` in charts and perhaps other elements.
|
||||
"""
|
||||
|
||||
add_p: Callable[[], CT_TextParagraph]
|
||||
p_lst: list[CT_TextParagraph]
|
||||
|
||||
bodyPr: CT_TextBodyProperties = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:bodyPr"
|
||||
)
|
||||
p: CT_TextParagraph = OneOrMore("a:p") # pyright: ignore[reportAssignmentType]
|
||||
|
||||
def clear_content(self):
|
||||
"""Remove all `a:p` children, but leave any others.
|
||||
|
||||
cf. lxml `_Element.clear()` method which removes all children.
|
||||
"""
|
||||
for p in self.p_lst:
|
||||
self.remove(p)
|
||||
|
||||
@property
|
||||
def defRPr(self) -> CT_TextCharacterProperties:
|
||||
"""`a:defRPr` element of required first `p` child, added with its ancestors if not present.
|
||||
|
||||
Used when element is a ``c:txPr`` in a chart and the `p` element is used only to specify
|
||||
formatting, not content.
|
||||
"""
|
||||
p = self.p_lst[0]
|
||||
pPr = p.get_or_add_pPr()
|
||||
defRPr = pPr.get_or_add_defRPr()
|
||||
return defRPr
|
||||
|
||||
@property
|
||||
def is_empty(self) -> bool:
|
||||
"""True if only a single empty `a:p` element is present."""
|
||||
ps = self.p_lst
|
||||
if len(ps) > 1:
|
||||
return False
|
||||
|
||||
if not ps:
|
||||
raise InvalidXmlError("p:txBody must have at least one a:p")
|
||||
|
||||
if ps[0].text != "":
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def new(cls):
|
||||
"""Return a new `p:txBody` element tree."""
|
||||
xml = cls._txBody_tmpl()
|
||||
txBody = parse_xml(xml)
|
||||
return txBody
|
||||
|
||||
@classmethod
|
||||
def new_a_txBody(cls) -> CT_TextBody:
|
||||
"""Return a new `a:txBody` element tree.
|
||||
|
||||
Suitable for use in a table cell and possibly other situations.
|
||||
"""
|
||||
xml = cls._a_txBody_tmpl()
|
||||
txBody = cast(CT_TextBody, parse_xml(xml))
|
||||
return txBody
|
||||
|
||||
@classmethod
|
||||
def new_p_txBody(cls):
|
||||
"""Return a new `p:txBody` element tree, suitable for use in an `p:sp` element."""
|
||||
xml = cls._p_txBody_tmpl()
|
||||
return parse_xml(xml)
|
||||
|
||||
@classmethod
|
||||
def new_txPr(cls):
|
||||
"""Return a `c:txPr` element tree.
|
||||
|
||||
Suitable for use in a chart object like data labels or tick labels.
|
||||
"""
|
||||
xml = (
|
||||
"<c:txPr %s>\n"
|
||||
" <a:bodyPr/>\n"
|
||||
" <a:lstStyle/>\n"
|
||||
" <a:p>\n"
|
||||
" <a:pPr>\n"
|
||||
" <a:defRPr/>\n"
|
||||
" </a:pPr>\n"
|
||||
" </a:p>\n"
|
||||
"</c:txPr>\n"
|
||||
) % nsdecls("c", "a")
|
||||
txPr = parse_xml(xml)
|
||||
return txPr
|
||||
|
||||
def unclear_content(self):
|
||||
"""Ensure p:txBody has at least one a:p child.
|
||||
|
||||
Intuitively, reverse a ".clear_content()" operation to minimum conformance with spec
|
||||
(single empty paragraph).
|
||||
"""
|
||||
if len(self.p_lst) > 0:
|
||||
return
|
||||
self.add_p()
|
||||
|
||||
@classmethod
|
||||
def _a_txBody_tmpl(cls):
|
||||
return "<a:txBody %s>\n" " <a:bodyPr/>\n" " <a:p/>\n" "</a:txBody>\n" % (nsdecls("a"))
|
||||
|
||||
@classmethod
|
||||
def _p_txBody_tmpl(cls):
|
||||
return (
|
||||
"<p:txBody %s>\n" " <a:bodyPr/>\n" " <a:p/>\n" "</p:txBody>\n" % (nsdecls("p", "a"))
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _txBody_tmpl(cls):
|
||||
return (
|
||||
"<p:txBody %s>\n"
|
||||
" <a:bodyPr/>\n"
|
||||
" <a:lstStyle/>\n"
|
||||
" <a:p/>\n"
|
||||
"</p:txBody>\n" % (nsdecls("a", "p"))
|
||||
)
|
||||
|
||||
|
||||
class CT_TextBodyProperties(BaseOxmlElement):
|
||||
"""`a:bodyPr` custom element class."""
|
||||
|
||||
_add_noAutofit: Callable[[], BaseOxmlElement]
|
||||
_add_normAutofit: Callable[[], CT_TextNormalAutofit]
|
||||
_add_spAutoFit: Callable[[], BaseOxmlElement]
|
||||
_remove_eg_textAutoFit: Callable[[], None]
|
||||
|
||||
noAutofit: BaseOxmlElement | None
|
||||
normAutofit: CT_TextNormalAutofit | None
|
||||
spAutoFit: BaseOxmlElement | None
|
||||
|
||||
eg_textAutoFit = ZeroOrOneChoice(
|
||||
(Choice("a:noAutofit"), Choice("a:normAutofit"), Choice("a:spAutoFit")),
|
||||
successors=("a:scene3d", "a:sp3d", "a:flatTx", "a:extLst"),
|
||||
)
|
||||
lIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"lIns", ST_Coordinate32, default=Emu(91440)
|
||||
)
|
||||
tIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"tIns", ST_Coordinate32, default=Emu(45720)
|
||||
)
|
||||
rIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"rIns", ST_Coordinate32, default=Emu(91440)
|
||||
)
|
||||
bIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"bIns", ST_Coordinate32, default=Emu(45720)
|
||||
)
|
||||
anchor: MSO_VERTICAL_ANCHOR | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"anchor", MSO_VERTICAL_ANCHOR
|
||||
)
|
||||
wrap: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"wrap", ST_TextWrappingType
|
||||
)
|
||||
|
||||
@property
|
||||
def autofit(self):
|
||||
"""The autofit setting for the text frame, a member of the `MSO_AUTO_SIZE` enumeration."""
|
||||
if self.noAutofit is not None:
|
||||
return MSO_AUTO_SIZE.NONE
|
||||
if self.normAutofit is not None:
|
||||
return MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
|
||||
if self.spAutoFit is not None:
|
||||
return MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT
|
||||
return None
|
||||
|
||||
@autofit.setter
|
||||
def autofit(self, value: MSO_AUTO_SIZE | None):
|
||||
if value is not None and value not in MSO_AUTO_SIZE:
|
||||
raise ValueError(
|
||||
f"only None or a member of the MSO_AUTO_SIZE enumeration can be assigned to"
|
||||
f" CT_TextBodyProperties.autofit, got {value}"
|
||||
)
|
||||
self._remove_eg_textAutoFit()
|
||||
if value == MSO_AUTO_SIZE.NONE:
|
||||
self._add_noAutofit()
|
||||
elif value == MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE:
|
||||
self._add_normAutofit()
|
||||
elif value == MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT:
|
||||
self._add_spAutoFit()
|
||||
|
||||
|
||||
class CT_TextCharacterProperties(BaseOxmlElement):
|
||||
"""Custom element class for `a:rPr`, `a:defRPr`, and `a:endParaRPr`.
|
||||
|
||||
'rPr' is short for 'run properties', and it corresponds to the |Font| proxy class.
|
||||
"""
|
||||
|
||||
get_or_add_hlinkClick: Callable[[], CT_Hyperlink]
|
||||
get_or_add_latin: Callable[[], CT_TextFont]
|
||||
_remove_latin: Callable[[], None]
|
||||
_remove_hlinkClick: Callable[[], None]
|
||||
|
||||
eg_fillProperties = ZeroOrOneChoice(
|
||||
(
|
||||
Choice("a:noFill"),
|
||||
Choice("a:solidFill"),
|
||||
Choice("a:gradFill"),
|
||||
Choice("a:blipFill"),
|
||||
Choice("a:pattFill"),
|
||||
Choice("a:grpFill"),
|
||||
),
|
||||
successors=(
|
||||
"a:effectLst",
|
||||
"a:effectDag",
|
||||
"a:highlight",
|
||||
"a:uLnTx",
|
||||
"a:uLn",
|
||||
"a:uFillTx",
|
||||
"a:uFill",
|
||||
"a:latin",
|
||||
"a:ea",
|
||||
"a:cs",
|
||||
"a:sym",
|
||||
"a:hlinkClick",
|
||||
"a:hlinkMouseOver",
|
||||
"a:rtl",
|
||||
"a:extLst",
|
||||
),
|
||||
)
|
||||
latin: CT_TextFont | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:latin",
|
||||
successors=(
|
||||
"a:ea",
|
||||
"a:cs",
|
||||
"a:sym",
|
||||
"a:hlinkClick",
|
||||
"a:hlinkMouseOver",
|
||||
"a:rtl",
|
||||
"a:extLst",
|
||||
),
|
||||
)
|
||||
hlinkClick: CT_Hyperlink | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:hlinkClick", successors=("a:hlinkMouseOver", "a:rtl", "a:extLst")
|
||||
)
|
||||
|
||||
lang: MSO_LANGUAGE_ID | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"lang", MSO_LANGUAGE_ID
|
||||
)
|
||||
sz: int | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"sz", ST_TextFontSize
|
||||
)
|
||||
b: bool | None = OptionalAttribute("b", XsdBoolean) # pyright: ignore[reportAssignmentType]
|
||||
i: bool | None = OptionalAttribute("i", XsdBoolean) # pyright: ignore[reportAssignmentType]
|
||||
u: MSO_TEXT_UNDERLINE_TYPE | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"u", MSO_TEXT_UNDERLINE_TYPE
|
||||
)
|
||||
|
||||
def _new_gradFill(self):
|
||||
return CT_GradientFillProperties.new_gradFill()
|
||||
|
||||
def add_hlinkClick(self, rId: str) -> CT_Hyperlink:
|
||||
"""Add an `a:hlinkClick` child element with r:id attribute set to `rId`."""
|
||||
hlinkClick = self.get_or_add_hlinkClick()
|
||||
hlinkClick.rId = rId
|
||||
return hlinkClick
|
||||
|
||||
|
||||
class CT_TextField(BaseOxmlElement):
|
||||
"""`a:fld` field element, for either a slide number or date field."""
|
||||
|
||||
get_or_add_rPr: Callable[[], CT_TextCharacterProperties]
|
||||
|
||||
rPr: CT_TextCharacterProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:rPr", successors=("a:pPr", "a:t")
|
||||
)
|
||||
t: BaseOxmlElement | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:t", successors=()
|
||||
)
|
||||
|
||||
@property
|
||||
def text(self) -> str: # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
"""The text of the `a:t` child element."""
|
||||
t = self.t
|
||||
if t is None:
|
||||
return ""
|
||||
return t.text or ""
|
||||
|
||||
|
||||
class CT_TextFont(BaseOxmlElement):
|
||||
"""Custom element class for `a:latin`, `a:ea`, `a:cs`, and `a:sym`.
|
||||
|
||||
These occur as child elements of CT_TextCharacterProperties, e.g. `a:rPr`.
|
||||
"""
|
||||
|
||||
typeface: str = RequiredAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"typeface", ST_TextTypeface
|
||||
)
|
||||
|
||||
|
||||
class CT_TextLineBreak(BaseOxmlElement):
|
||||
"""`a:br` line break element"""
|
||||
|
||||
get_or_add_rPr: Callable[[], CT_TextCharacterProperties]
|
||||
|
||||
rPr = ZeroOrOne("a:rPr", successors=())
|
||||
|
||||
@property
|
||||
def text(self): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
"""Unconditionally a single vertical-tab character.
|
||||
|
||||
A line break element can contain no text other than the implicit line feed it
|
||||
represents.
|
||||
"""
|
||||
return "\v"
|
||||
|
||||
|
||||
class CT_TextNormalAutofit(BaseOxmlElement):
|
||||
"""`a:normAutofit` element specifying fit text to shape font reduction, etc."""
|
||||
|
||||
fontScale = OptionalAttribute(
|
||||
"fontScale", ST_TextFontScalePercentOrPercentString, default=100.0
|
||||
)
|
||||
|
||||
|
||||
class CT_TextParagraph(BaseOxmlElement):
|
||||
"""`a:p` custom element class"""
|
||||
|
||||
get_or_add_endParaRPr: Callable[[], CT_TextCharacterProperties]
|
||||
get_or_add_pPr: Callable[[], CT_TextParagraphProperties]
|
||||
r_lst: list[CT_RegularTextRun]
|
||||
_add_br: Callable[[], CT_TextLineBreak]
|
||||
_add_r: Callable[[], CT_RegularTextRun]
|
||||
|
||||
pPr: CT_TextParagraphProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:pPr", successors=("a:r", "a:br", "a:fld", "a:endParaRPr")
|
||||
)
|
||||
r = ZeroOrMore("a:r", successors=("a:endParaRPr",))
|
||||
br = ZeroOrMore("a:br", successors=("a:endParaRPr",))
|
||||
endParaRPr: CT_TextCharacterProperties | None = ZeroOrOne(
|
||||
"a:endParaRPr", successors=()
|
||||
) # pyright: ignore[reportAssignmentType]
|
||||
|
||||
def add_br(self) -> CT_TextLineBreak:
|
||||
"""Return a newly appended `a:br` element."""
|
||||
return self._add_br()
|
||||
|
||||
def add_r(self, text: str | None = None) -> CT_RegularTextRun:
|
||||
"""Return a newly appended `a:r` element."""
|
||||
r = self._add_r()
|
||||
if text:
|
||||
r.text = text
|
||||
return r
|
||||
|
||||
def append_text(self, text: str):
|
||||
"""Append `a:r` and `a:br` elements to `p` based on `text`.
|
||||
|
||||
Any `\n` or `\v` (vertical-tab) characters in `text` delimit `a:r` (run) elements and
|
||||
themselves are translated to `a:br` (line-break) elements. The vertical-tab character
|
||||
appears in clipboard text from PowerPoint at "soft" line-breaks (new-line, but not new
|
||||
paragraph).
|
||||
"""
|
||||
for idx, r_str in enumerate(re.split("\n|\v", text)):
|
||||
# ---breaks are only added _between_ items, not at start---
|
||||
if idx > 0:
|
||||
self.add_br()
|
||||
# ---runs that would be empty are not added---
|
||||
if r_str:
|
||||
self.add_r(r_str)
|
||||
|
||||
@property
|
||||
def content_children(self) -> tuple[CT_RegularTextRun | CT_TextLineBreak | CT_TextField, ...]:
|
||||
"""Sequence containing text-container child elements of this `a:p` element.
|
||||
|
||||
These include `a:r`, `a:br`, and `a:fld`.
|
||||
"""
|
||||
return tuple(
|
||||
e for e in self if isinstance(e, (CT_RegularTextRun, CT_TextLineBreak, CT_TextField))
|
||||
)
|
||||
|
||||
@property
|
||||
def text(self) -> str: # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
"""str text contained in this paragraph."""
|
||||
# ---note this shadows the lxml _Element.text---
|
||||
return "".join([child.text for child in self.content_children])
|
||||
|
||||
def _new_r(self):
|
||||
r_xml = "<a:r %s><a:t/></a:r>" % nsdecls("a")
|
||||
return parse_xml(r_xml)
|
||||
|
||||
|
||||
class CT_TextParagraphProperties(BaseOxmlElement):
|
||||
"""`a:pPr` custom element class."""
|
||||
|
||||
get_or_add_defRPr: Callable[[], CT_TextCharacterProperties]
|
||||
_add_lnSpc: Callable[[], CT_TextSpacing]
|
||||
_add_spcAft: Callable[[], CT_TextSpacing]
|
||||
_add_spcBef: Callable[[], CT_TextSpacing]
|
||||
_remove_lnSpc: Callable[[], None]
|
||||
_remove_spcAft: Callable[[], None]
|
||||
_remove_spcBef: Callable[[], None]
|
||||
|
||||
_tag_seq = (
|
||||
"a:lnSpc",
|
||||
"a:spcBef",
|
||||
"a:spcAft",
|
||||
"a:buClrTx",
|
||||
"a:buClr",
|
||||
"a:buSzTx",
|
||||
"a:buSzPct",
|
||||
"a:buSzPts",
|
||||
"a:buFontTx",
|
||||
"a:buFont",
|
||||
"a:buNone",
|
||||
"a:buAutoNum",
|
||||
"a:buChar",
|
||||
"a:buBlip",
|
||||
"a:tabLst",
|
||||
"a:defRPr",
|
||||
"a:extLst",
|
||||
)
|
||||
lnSpc: CT_TextSpacing | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:lnSpc", successors=_tag_seq[1:]
|
||||
)
|
||||
spcBef: CT_TextSpacing | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:spcBef", successors=_tag_seq[2:]
|
||||
)
|
||||
spcAft: CT_TextSpacing | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:spcAft", successors=_tag_seq[3:]
|
||||
)
|
||||
defRPr: CT_TextCharacterProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:defRPr", successors=_tag_seq[16:]
|
||||
)
|
||||
lvl: int = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"lvl", ST_TextIndentLevelType, default=0
|
||||
)
|
||||
algn: PP_PARAGRAPH_ALIGNMENT | None = OptionalAttribute(
|
||||
"algn", PP_PARAGRAPH_ALIGNMENT
|
||||
) # pyright: ignore[reportAssignmentType]
|
||||
del _tag_seq
|
||||
|
||||
@property
|
||||
def line_spacing(self) -> float | Length | None:
|
||||
"""The spacing between baselines of successive lines in this paragraph.
|
||||
|
||||
A float value indicates a number of lines. A |Length| value indicates a fixed spacing.
|
||||
Value is contained in `./a:lnSpc/a:spcPts/@val` or `./a:lnSpc/a:spcPct/@val`. Value is
|
||||
|None| if no element is present.
|
||||
"""
|
||||
lnSpc = self.lnSpc
|
||||
if lnSpc is None:
|
||||
return None
|
||||
if lnSpc.spcPts is not None:
|
||||
return lnSpc.spcPts.val
|
||||
return cast(CT_TextSpacingPercent, lnSpc.spcPct).val
|
||||
|
||||
@line_spacing.setter
|
||||
def line_spacing(self, value: float | Length | None):
|
||||
self._remove_lnSpc()
|
||||
if value is None:
|
||||
return
|
||||
if isinstance(value, Length):
|
||||
self._add_lnSpc().set_spcPts(value)
|
||||
else:
|
||||
self._add_lnSpc().set_spcPct(value)
|
||||
|
||||
@property
|
||||
def space_after(self) -> Length | None:
|
||||
"""The EMU equivalent of the centipoints value in `./a:spcAft/a:spcPts/@val`."""
|
||||
spcAft = self.spcAft
|
||||
if spcAft is None:
|
||||
return None
|
||||
spcPts = spcAft.spcPts
|
||||
if spcPts is None:
|
||||
return None
|
||||
return spcPts.val
|
||||
|
||||
@space_after.setter
|
||||
def space_after(self, value: Length | None):
|
||||
self._remove_spcAft()
|
||||
if value is not None:
|
||||
self._add_spcAft().set_spcPts(value)
|
||||
|
||||
@property
|
||||
def space_before(self):
|
||||
"""The EMU equivalent of the centipoints value in `./a:spcBef/a:spcPts/@val`."""
|
||||
spcBef = self.spcBef
|
||||
if spcBef is None:
|
||||
return None
|
||||
spcPts = spcBef.spcPts
|
||||
if spcPts is None:
|
||||
return None
|
||||
return spcPts.val
|
||||
|
||||
@space_before.setter
|
||||
def space_before(self, value: Length | None):
|
||||
self._remove_spcBef()
|
||||
if value is not None:
|
||||
self._add_spcBef().set_spcPts(value)
|
||||
|
||||
|
||||
class CT_TextSpacing(BaseOxmlElement):
|
||||
"""Used for `a:lnSpc`, `a:spcBef`, and `a:spcAft` elements."""
|
||||
|
||||
get_or_add_spcPct: Callable[[], CT_TextSpacingPercent]
|
||||
get_or_add_spcPts: Callable[[], CT_TextSpacingPoint]
|
||||
_remove_spcPct: Callable[[], None]
|
||||
_remove_spcPts: Callable[[], None]
|
||||
|
||||
# this should actually be a OneAndOnlyOneChoice, but that's not
|
||||
# implemented yet.
|
||||
spcPct: CT_TextSpacingPercent | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:spcPct"
|
||||
)
|
||||
spcPts: CT_TextSpacingPoint | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
||||
"a:spcPts"
|
||||
)
|
||||
|
||||
def set_spcPct(self, value: float):
|
||||
"""Set spacing to `value` lines, e.g. 1.75 lines.
|
||||
|
||||
A ./a:spcPts child is removed if present.
|
||||
"""
|
||||
self._remove_spcPts()
|
||||
spcPct = self.get_or_add_spcPct()
|
||||
spcPct.val = value
|
||||
|
||||
def set_spcPts(self, value: Length):
|
||||
"""Set spacing to `value` points. A ./a:spcPct child is removed if present."""
|
||||
self._remove_spcPct()
|
||||
spcPts = self.get_or_add_spcPts()
|
||||
spcPts.val = value
|
||||
|
||||
|
||||
class CT_TextSpacingPercent(BaseOxmlElement):
|
||||
"""`a:spcPct` element, specifying spacing in thousandths of a percent in its `val` attribute."""
|
||||
|
||||
val: float = RequiredAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"val", ST_TextSpacingPercentOrPercentString
|
||||
)
|
||||
|
||||
|
||||
class CT_TextSpacingPoint(BaseOxmlElement):
|
||||
"""`a:spcPts` element, specifying spacing in centipoints in its `val` attribute."""
|
||||
|
||||
val: Length = RequiredAttribute( # pyright: ignore[reportAssignmentType]
|
||||
"val", ST_TextSpacingPoint
|
||||
)
|
||||
Reference in New Issue
Block a user