Skip to content

feature: tab stops #261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
38 changes: 38 additions & 0 deletions docs/api/enum/WdTabAlignment.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.. _WdTabAlignment:

``WD_TAB_ALIGNMENT``
====================

Specifies the tab stop alignment to apply.

----

LEFT
Left-aligned.

CENTER
Center-aligned.

RIGHT
Right-aligned.

DECIMAL
Decimal-aligned.

BAR
Bar-aligned.

LIST
List-aligned. (deprecated)

CLEAR
Clear an inherited tab stop.

END
Right-aligned. (deprecated)

NUM
Left-aligned. (deprecated)

START
Left-aligned. (deprecated)
26 changes: 26 additions & 0 deletions docs/api/enum/WdTabLeader.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.. _WdTabLeader:

``WD_TAB_LEADER``
=================

Specifies the character to use as the leader with formatted tabs.

----

SPACES
Spaces. Default.

DOTS
Dots.

DASHES
Dashes.

LINES
Double lines.

HEAVY
A heavy line.

MIDDLE_DOT
A vertically-centered dot.
4 changes: 3 additions & 1 deletion docs/api/enum/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ can be found here:
WdColorIndex
WdLineSpacing
WdOrientation
WdRowAlignment
WdSectionStart
WdStyleType
WdRowAlignment
WdTabAlignment
WdTabLeader
WdTableDirection
WdUnderline
74 changes: 74 additions & 0 deletions docx/enum/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,80 @@ class WD_LINE_SPACING(XmlEnumeration):
)


class WD_TAB_ALIGNMENT(XmlEnumeration):
"""
Specifies the tab stop alignment to apply.
"""

__ms_name__ = 'WdTabAlignment'

__url__ = 'https://msdn.microsoft.com/EN-US/library/office/ff195609.aspx'

__members__ = (
XmlMappedEnumMember(
'LEFT', 0, 'left', 'Left-aligned.'
),
XmlMappedEnumMember(
'CENTER', 1, 'center', 'Center-aligned.'
),
XmlMappedEnumMember(
'RIGHT', 2, 'right', 'Right-aligned.'
),
XmlMappedEnumMember(
'DECIMAL', 3, 'decimal', 'Decimal-aligned.'
),
XmlMappedEnumMember(
'BAR', 4, 'bar', 'Bar-aligned.'
),
XmlMappedEnumMember(
'LIST', 6, 'list', 'List-aligned. (deprecated)'
),
XmlMappedEnumMember(
'CLEAR', 101, 'clear', 'Clear an inherited tab stop.'
),
XmlMappedEnumMember(
'END', 102, 'end', 'Right-aligned. (deprecated)'
),
XmlMappedEnumMember(
'NUM', 103, 'num', 'Left-aligned. (deprecated)'
),
XmlMappedEnumMember(
'START', 104, 'start', 'Left-aligned. (deprecated)'
),
)


class WD_TAB_LEADER(XmlEnumeration):
"""
Specifies the character to use as the leader with formatted tabs.
"""

__ms_name__ = 'WdTabLeader'

__url__ = 'https://msdn.microsoft.com/en-us/library/office/ff845050.aspx'

__members__ = (
XmlMappedEnumMember(
'SPACES', 0, 'none', 'Spaces. Default.'
),
XmlMappedEnumMember(
'DOTS', 1, 'dot', 'Dots.'
),
XmlMappedEnumMember(
'DASHES', 2, 'hyphen', 'Dashes.'
),
XmlMappedEnumMember(
'LINES', 3, 'underscore', 'Double lines.'
),
XmlMappedEnumMember(
'HEAVY', 4, 'heavy', 'A heavy line.'
),
XmlMappedEnumMember(
'MIDDLE_DOT', 5, 'middleDot', 'A vertically-centered dot.'
),
)


class WD_UNDERLINE(XmlEnumeration):
"""
Specifies the style of underline applied to a run of characters.
Expand Down
5 changes: 4 additions & 1 deletion docx/oxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,9 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
from .text.paragraph import CT_P
register_element_cls('w:p', CT_P)

from .text.parfmt import CT_Ind, CT_Jc, CT_PPr, CT_Spacing, CT_TabStops
from .text.parfmt import (
CT_Ind, CT_Jc, CT_PPr, CT_Spacing, CT_TabStop, CT_TabStops
)
register_element_cls('w:ind', CT_Ind)
register_element_cls('w:jc', CT_Jc)
register_element_cls('w:keepLines', CT_OnOff)
Expand All @@ -190,6 +192,7 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('w:pPr', CT_PPr)
register_element_cls('w:pStyle', CT_String)
register_element_cls('w:spacing', CT_Spacing)
register_element_cls('w:tab', CT_TabStop)
register_element_cls('w:tabs', CT_TabStops)
register_element_cls('w:widowControl', CT_OnOff)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DKWoods All these changes are merged in here: https://github.com/python-openxml/python-docx/commits/feature/tabstops

..these comments are just so you can know for next time. Check the merged commit for actual specifics.

  • w:tab/CT_TabStop is not needed to pass this test. It will have to wait it's turn :) Dropped from this commit.


Expand Down
28 changes: 27 additions & 1 deletion docx/oxml/text/parfmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
Custom element classes related to paragraph properties (CT_PPr).
"""

from ...enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
from ...enum.text import (
WD_ALIGN_PARAGRAPH, WD_LINE_SPACING, WD_TAB_ALIGNMENT, WD_TAB_LEADER
)
from ...shared import Length
from ..simpletypes import ST_SignedTwipsMeasure, ST_TwipsMeasure
from ..xmlchemy import (
Expand Down Expand Up @@ -315,8 +317,32 @@ class CT_Spacing(BaseOxmlElement):
lineRule = OptionalAttribute('w:lineRule', WD_LINE_SPACING)


class CT_TabStop(BaseOxmlElement):
"""
``<w:tab>`` element, representing an individual tab stop.
"""
val = RequiredAttribute('w:val', WD_TAB_ALIGNMENT)
leader = OptionalAttribute(
'w:leader', WD_TAB_LEADER, default=WD_TAB_LEADER.SPACES
)
pos = RequiredAttribute('w:pos', ST_SignedTwipsMeasure)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CT_TabStop is not needed to pass this test. Dropped from this commit.


class CT_TabStops(BaseOxmlElement):
"""
``<w:tabs>`` element, container for a sorted sequence of tab stops.
"""
tab = OneOrMore('w:tab', successors=())

def insert_tab_in_order(self, pos, align, leader):
"""
Insert a newly created `w:tab` child element in *pos* order.
"""
new_tab = self._new_tab()
new_tab.pos, new_tab.val, new_tab.leader = pos, align, leader
for tab in self.tab_lst:
if new_tab.pos < tab.pos:
tab.addprevious(new_tab)
return new_tab
self.append(new_tab)
return new_tab
73 changes: 73 additions & 0 deletions docx/text/tabstops.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
)

from ..shared import ElementProxy
from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER


class TabStops(ElementProxy):
Expand All @@ -25,6 +26,19 @@ def __init__(self, element):
super(TabStops, self).__init__(element, None)
self._pPr = element

def __delitem__(self, idx):
"""
Remove the tab at offset *idx* in this sequence.
"""
tabs = self._pPr.tabs
try:
tabs.remove(tabs[idx])
except (AttributeError, IndexError):
raise IndexError('tab index out of range')

if len(tabs) == 0:
self._pPr.remove(tabs)

def __getitem__(self, idx):
"""
Enables list-style access by index.
Expand All @@ -51,6 +65,25 @@ def __len__(self):
return 0
return len(tabs.tab_lst)

def add_tab_stop(self, position, alignment=WD_TAB_ALIGNMENT.LEFT,
leader=WD_TAB_LEADER.SPACES):
"""
Add a new tab stop at *position*. Tab alignment defaults to left, but
may be specified by passing a member of the :ref:`WdTabAlignment`
enumeration as *alignment*. An optional leader character can be
specified by passing a member of the :ref:`WdTabLeader` enumeration
as *leader*.
"""
tabs = self._pPr.get_or_add_tabs()
tab = tabs.insert_tab_in_order(position, alignment, leader)
return TabStop(tab)

def clear_all(self):
"""
Remove all custom tab stops.
"""
self._pPr._remove_tabs()


class TabStop(ElementProxy):
"""
Expand All @@ -63,3 +96,43 @@ class TabStop(ElementProxy):
def __init__(self, element):
super(TabStop, self).__init__(element, None)
self._tab = element

@property
def alignment(self):
"""
A member of :ref:`WdTabAlignment` specifying the alignment setting
for this tab stop.
"""
return self._tab.val

@alignment.setter
def alignment(self, value):
self._tab.val = value

@property
def leader(self):
"""
A member of :ref:`WdTabLeader` specifying a repeating character used
as a "leader", filling in the space spanned by this tab. Assigning
|None| produces the same result as assigning `WD_TAB_LEADER.SPACES`.
"""
return self._tab.leader

@leader.setter
def leader(self, value):
self._tab.leader = value

@property
def position(self):
"""
The distance (in EMU) of this tab stop from the inside edge of the
paragraph. May be positive or negative.
"""
return self._tab.pos

@position.setter
def position(self, value):
if self._tab.pos != value:
self._tab.getparent().insert_tab_in_order(value, self._tab.val,
self._tab.leader)
self._tab.getparent().remove(self._element)
Loading