带有 CTL(复杂文本布局)语言的 python-docx add_style

标签 python python-docx

我想要完成的事情:

  • 使用用户定义的波斯语字体和大小(CTL 语言)在 python-docx 中创建段落样式

问题:

  • 我可以使用非 CTL 语言(例如英语)执行此操作:

    from docx import Document
    from docx.enum.style import WD_STYLE_TYPE
    from docx.shared import Pt
    
    user_font_name = 'FreeMono'
    user_font_size = 14
    
    doc = Document()
    my_style = doc.styles.add_style('style_name',WD_STYLE_TYPE.PARAGRAPH)
    my_font = my_style.font
    my_font.name = user_font_name
    my_font.size = Pt(user_font_size)
    p = doc.add_paragraph('some text',my_style)
    
    # persian_p = doc.add_paragraph('نوشته',my_style)
    # FreeMono supports Persian language so the problem is not the font
    
    doc.save('file.docx')
    
  • 但是,如果我将文本更改为波斯文本,其字体不会更改为指定的字体。

为什么会这样:

  • 我指定的字体只改变西文字体风格,对CTL字体系列没有任何影响

我怎么知道的:

  • 如果我用 LibreOffice 打开 docx 文件并打开样式并进入字体部分,我可以看到我指定的字体和大小在“Western Text Font Family”中,但不在“CTL Font Family”中。结果,我的 CTL 文本字体变成了默认字体。

附加信息:

  1. 我在 Linux 上使用 LibreOffice
  2. 更改默认样式在这种情况下对我没有任何好处,因为我希望用户指定字体名称和大小。
  3. 我没有更改 xml 文件的经验(更不用说 docx xml 文件了)
  4. python-docx版本为0.8.6

最佳答案

在查看 docx 文件数小时后,我惊恐地意识到,答案就在文档的 style.xml 文件中。这是为有类似问题的人修复它的一种方法:

文本方向问题:

  • 如果您曾经输入过阿拉伯语或波斯语,您可能会发现从右到左对齐文本并不能解决所有问题。因为如果你不改变文本方向,那么光标和标点符号会留在屏幕的最右边(而不是跟在最后一个字母后面)并且如果你需要的话就没有右对齐。现在,因为我无法更改 python-docx 中的文本方向,即使将 document.xml 的“textDirection”值从“lrTb”(左-右/上-下)更改为“rlTb”,我不得不使用 LibreOffice 制作文档并将其默认段落样式(“正常”)更改为我想要的样式(rtl 文本方向等)。这实际上也为以后节省了大量时间,因为您不需要在 Python 中执行此操作。

字体变化问题的xml解释:

  • 默认样式已更改的文档在其 style.xml 文件中显示了一些不同的内容。 在 "w:rPr"下的普通段落样式中,您可以看到有一个额外的 "w:szCs"确定复杂脚本字体的大小(您不能通过更改 style.font.size 来更改)和在 "w :rFonts""cs"的值现在是我指定的波斯字体。此外,“w:lang”值“bidi”现在是“fa-IR”(波斯语)。这是我正在谈论的 xml 部分:

    <w:rPr>
    <w:rFonts w:ascii="FreeMono" w:hAnsi="FreeMono" w:cs="FreeFarsi"/>
    <w:sz w:val="40"/>
    <w:rtl/>
    <w:cs/>
    <w:szCs w:val="40"/>
    <w:lang w:val="en-Us" w:bidi="fa-IR"/>
    </w:rPr>
    
  • 现在更改 style.font.size 只会更改“sz”值(西方字体大小),而不会对“szCs”值(cs 字体大小)做任何事情。同样,style.font.name 仅更改“w:rFonts”的“ascii”和“hAnsi”值,对“cs”值不做任何操作。因此,要更改这些值,我必须更改 python 中的样式元素。

解决方案:

from docx import Document
from docx.shared import Pt

#path to doc with altered style:
base_doc_location = 'base.docx'
doc = Document(base_doc_location)
my_style = doc.styles['Normal']

# define your desired fonts
user_cs_font_size = 16
user_cs_font_name = 'FreeFarsi'
user_en_font_size = 12
user_en_font_name = 'FreeMono'

# get <w:rPr> element of this style
rpr = my_style.element.rPr

#==================================================
'''This probably isn't necessary if you already
have a document with altered style, but just to be
safe I'm going to add this here'''

if rpr.rFonts is None:
    rpr._add_rFonts()
if rpr.sz is None:
    rpr._add_sz()
#==================================================

'''Get the nsmap string for rpr. This is that "w:"
at the start of elements and element values in xml.
Like these:
    <w:rPr>
    <w:rFonts>
    w:val

The nsmap is like a url:
http://schemas.openxmlformats.org/...

Now w:rPr translates to:
{nsmap url string}rPr

So I made the w_nsmap string like this:'''

w_nsmap = '{'+rpr.nsmap['w']+'}'
#==================================================

'''Because I didn't find any better ways to get an
element based on its tag here's a not so great way
of getting it:
'''
szCs = None
lang = None

for element in rpr:
    if element.tag == w_nsmap + 'szCs':
        szCs = element
    elif element.tag == w_nsmap + 'lang':
        lang = element

'''if there is a szCs and lang element in your style
those variables will be assigned to it, and if not
we make those elements and add them to rpr'''

if szCs is None:
    szCs = rpr.makeelement(w_nsmap+'szCs',nsmap=rpr.nsmap)
if lang is None:
    lang = rpr.makeelement(w_nsmap+'lang',nsmap =rpr.nsmap)

rpr.append(szCs)
rpr.append(lang)
#==================================================

'''Now to set our desired values to these elements
we have to get attrib dictionary of these elements
and set the name of value as key and our value as
value for that dict'''

szCs_attrib = szCs.attrib
lang_attrib = lang.attrib
rFonts_atr = rpr.rFonts.attrib

'''sz and szCs values are string values and 2 times
the font size so if you want font size to be 11 you
have to set sz (for western fonts) or szCs (for CTL
fonts) to "22" '''
szCs_attrib[w_nsmap+'val'] =str(int(user_cs_font_size*2))

'''Now to change cs font and bidi lang values'''
rFonts_atr[w_nsmap+'cs'] = user_cs_font_name
lang_attrib[w_nsmap+'bidi'] = 'fa-IR' # For Persian
#==================================================

'''Because we changed default style we don't even
need to set style every time we add a new paragraph
And if you change font name or size the normal way
it won't change these cs values so you can have a
font for CTL language and a different font for
western language
'''
persian_p = doc.add_paragraph('نوشته')
en_font = my_style.font
en_font.name = user_en_font_name
en_font.size = Pt(user_en_font_size)
english_p = doc.add_paragraph('some text')

doc.save('ex.docx')

编辑(代码改进):
我评论了可以进行一些改进的行,并将更好的行放在它们下面。

#rpr = my_style.element.rPr # If None it'll throw errors later
rpr = my_style.element.get_or_add_rPr() # this avoids potential errors
#if rpr.rFonts is None:
#    rpr._add_rFonts()
rFonts = rpr.get_or_add_rFonts()
#if rpr.sz is None:
#    rpr._add_sz()
rpr.get_or_add_sz()

#by importing these you can make elements and set values quicker
from docx.oxml.shared import OxmlElement, qn
#szCs = rpr.makeelement(w_nsmap+'szCs',nsmap=rpr.nsmap)
szCs = OxmlElement('w:szCs')
#lang = rpr.makeelement(w_nsmap+'lang',nsmap =rpr.nsmap)
lang = OxmlElement('w:lang')

#szCs_attrib = szCs.attrib
#lang_attrib = lang.attrib
#rFonts_atr = rpr.rFonts.attrib
#szCs_attrib[w_nsmap+'val'] =str(int(user_cs_font_size*2))
#rFonts_atr[w_nsmap+'cs'] = user_cs_font_name
#lang_attrib[w_nsmap+'bidi'] = 'fa-IR'

szCs.set(qn('w:val'),str(int(user_cs_font_size*2)))
lang.set(qn('w:bidi'),'fa-IR')
rFonts.set(qn('w:cs'),user_cs_font_name)

关于带有 CTL(复杂文本布局)语言的 python-docx add_style,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45627652/

相关文章:

python - 将函数应用于 Dask : How do you specify the grouped Dataframe as argument in the function? 中的分组数据帧

python - 在 Python 中生成多列值的条件语句

python - 使用 Python 高效查找原根模 n?

python - 使用python docx合并word文档

python - 使用 BeautifulSoup 修改 HTML

python - 使用 Pandas 对 float 列进行分组

Python docx add_paragraph() 插入前导换行符

python - docx.opc.exceptions.PackageNotFoundError : Package not found at

python - 使用 python-docx 添加页码