python-docx add_style 与 CTL(复杂文本布局)语言

ROA*_*OAR 3 python python-docx

我\xe2\x80\x99m 试图完成的任务:

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

问题:

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

    \n\n
    from docx import Document\nfrom docx.enum.style import WD_STYLE_TYPE\nfrom docx.shared import Pt\n\nuser_font_name = 'FreeMono'\nuser_font_size = 14\n\ndoc = Document()\nmy_style = doc.styles.add_style('style_name',WD_STYLE_TYPE.PARAGRAPH)\nmy_font = my_style.font\nmy_font.name = user_font_name\nmy_font.size = Pt(user_font_size)\np = doc.add_paragraph('some text',my_style)\n\n# persian_p = doc.add_paragraph('\xd9\x86\xd9\x88\xd8\xb4\xd8\xaa\xd9\x87',my_style)\n# FreeMono supports Persian language so the problem is not the font\n\ndoc.save('file.docx')\n
    Run Code Online (Sandbox Code Playgroud)
  • \n
  • 但是,如果我将文本更改为波斯语文本,其字体将\xe2\x80\x99t 更改为指定字体。

  • \n
\n\n

为什么会发生这种情况:

\n\n
    \n
  • 我指定的字体仅更改西方字体系列的样式,并且 \xe2\x80\x99 对 CTL 字体系列没有任何作用
  • \n
\n\n

我怎么知道这一点:

\n\n
    \n
  • 如果我使用 LibreOffice 打开 docx 文件并打开样式并进入字体部分,我可以看到我指定的字体和大小位于 \xe2\x80\x9cWestern Text Font Family\xe2\x80\x9d 中,但不在 \ xe2\x80\x9cCTL 字体系列\xe2\x80\x9d。因此,我的 CTL 文本字体成为默认字体。
  • \n
\n\n

附加信息:

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

ROA*_*OAR 5

经过几个小时的研究 docx 文件后,我惊恐地意识到答案就在文档的 style.xml 文件中。Here\xe2\x80\x99s 是一种为有类似问题的人修复它的方法:

\n\n

文本方向问题:

\n\n
    \n
  • 如果您曾经输入过阿拉伯语或波斯语,您可能会发现从右到左对齐文本并不能解决您的所有问题。因为如果您不更改文本方向,则光标和标点符号将保留在屏幕的最右侧(而不是跟随最后一个字母),并且如果需要,则不会右对齐。现在因为即使通过从 \xe2\x80\x98lrTb\xe2\x80\x99 更改 document.xml 的 \xe2\x80\x9ctextDirection\xe2\x80\x9d 值,我也无法\xe2\x80\x99t 更改 python-docx 中的文本方向(从左到右/从上到下)到 \xe2\x80\x98rlTb\xe2\x80\x99,我必须使用 LibreOffice 制作一个文档并更改其默认段落样式 (\xe2\x80\x98Normal\xe2\x80\x99 )到我的想法(rtl文本方向等)。这实际上也节省了很多时间,因为你不需要在 python 中执行此操作。
  • \n
\n\n

字体改变问题的xml解释:

\n\n
    \n
  • 更改了默认样式的文档在其 style.xml 文件中显示了一些不同的内容。\n在“w:rPr”下的普通段落样式中,您可以看到有一个附加的“w:szCs”,用于确定复杂脚本的大小字体(您可以通过更改 style.font.size\xe2\x80\x99t 进行更改),并且在“w:rFonts”中,“cs”的值现在是我指定的波斯字体。另外,“w:lang”值 \xe2\x80\x9cbidi\xe2\x80\x9d 现在为 \xe2\x80\x9cfa-IR\xe2\x80\x9d(对于波斯语)。这里\xe2\x80\x99s是我\xe2\x80\x99m谈论的xml部分:

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

  • \n
\n\n

解决方案:

\n\n
from docx import Document\nfrom docx.shared import Pt\n\n#path to doc with altered style:\nbase_doc_location = \'base.docx\'\ndoc = Document(base_doc_location)\nmy_style = doc.styles[\'Normal\']\n\n# define your desired fonts\nuser_cs_font_size = 16\nuser_cs_font_name = \'FreeFarsi\'\nuser_en_font_size = 12\nuser_en_font_name = \'FreeMono\'\n\n# get <w:rPr> element of this style\nrpr = my_style.element.rPr\n\n#==================================================\n\'\'\'This probably isn\'t necessary if you already\nhave a document with altered style, but just to be\nsafe I\'m going to add this here\'\'\'\n\nif rpr.rFonts is None:\n    rpr._add_rFonts()\nif rpr.sz is None:\n    rpr._add_sz()\n#==================================================\n\n\'\'\'Get the nsmap string for rpr. This is that "w:"\nat the start of elements and element values in xml.\nLike these:\n    <w:rPr>\n    <w:rFonts>\n    w:val\n\nThe nsmap is like a url:\nhttp://schemas.openxmlformats.org/...\n\nNow w:rPr translates to:\n{nsmap url string}rPr\n\nSo I made the w_nsmap string like this:\'\'\'\n\nw_nsmap = \'{\'+rpr.nsmap[\'w\']+\'}\'\n#==================================================\n\n\'\'\'Because I didn\'t find any better ways to get an\nelement based on its tag here\'s a not so great way\nof getting it:\n\'\'\'\nszCs = None\nlang = None\n\nfor element in rpr:\n    if element.tag == w_nsmap + \'szCs\':\n        szCs = element\n    elif element.tag == w_nsmap + \'lang\':\n        lang = element\n\n\'\'\'if there is a szCs and lang element in your style\nthose variables will be assigned to it, and if not\nwe make those elements and add them to rpr\'\'\'\n\nif szCs is None:\n    szCs = rpr.makeelement(w_nsmap+\'szCs\',nsmap=rpr.nsmap)\nif lang is None:\n    lang = rpr.makeelement(w_nsmap+\'lang\',nsmap =rpr.nsmap)\n\nrpr.append(szCs)\nrpr.append(lang)\n#==================================================\n\n\'\'\'Now to set our desired values to these elements\nwe have to get attrib dictionary of these elements\nand set the name of value as key and our value as\nvalue for that dict\'\'\'\n\nszCs_attrib = szCs.attrib\nlang_attrib = lang.attrib\nrFonts_atr = rpr.rFonts.attrib\n\n\'\'\'sz and szCs values are string values and 2 times\nthe font size so if you want font size to be 11 you\nhave to set sz (for western fonts) or szCs (for CTL\nfonts) to "22" \'\'\'\nszCs_attrib[w_nsmap+\'val\'] =str(int(user_cs_font_size*2))\n\n\'\'\'Now to change cs font and bidi lang values\'\'\'\nrFonts_atr[w_nsmap+\'cs\'] = user_cs_font_name\nlang_attrib[w_nsmap+\'bidi\'] = \'fa-IR\' # For Persian\n#==================================================\n\n\'\'\'Because we changed default style we don\'t even\nneed to set style every time we add a new paragraph\nAnd if you change font name or size the normal way\nit won\'t change these cs values so you can have a\nfont for CTL language and a different font for\nwestern language\n\'\'\'\npersian_p = doc.add_paragraph(\'\xd9\x86\xd9\x88\xd8\xb4\xd8\xaa\xd9\x87\')\nen_font = my_style.font\nen_font.name = user_en_font_name\nen_font.size = Pt(user_en_font_size)\nenglish_p = doc.add_paragraph(\'some text\')\n\ndoc.save(\'ex.docx\')\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
#rpr = my_style.element.rPr # If None it\'ll throw errors later\nrpr = my_style.element.get_or_add_rPr() # this avoids potential errors\n#if rpr.rFonts is None:\n#    rpr._add_rFonts()\nrFonts = rpr.get_or_add_rFonts()\n#if rpr.sz is None:\n#    rpr._add_sz()\nrpr.get_or_add_sz()\n\n#by importing these you can make elements and set values quicker\nfrom docx.oxml.shared import OxmlElement, qn\n#szCs = rpr.makeelement(w_nsmap+\'szCs\',nsmap=rpr.nsmap)\nszCs = OxmlElement(\'w:szCs\')\n#lang = rpr.makeelement(w_nsmap+\'lang\',nsmap =rpr.nsmap)\nlang = OxmlElement(\'w:lang\')\n\n#szCs_attrib = szCs.attrib\n#lang_attrib = lang.attrib\n#rFonts_atr = rpr.rFonts.attrib\n#szCs_attrib[w_nsmap+\'val\'] =str(int(user_cs_font_size*2))\n#rFonts_atr[w_nsmap+\'cs\'] = user_cs_font_name\n#lang_attrib[w_nsmap+\'bidi\'] = \'fa-IR\'\n\nszCs.set(qn(\'w:val\'),str(int(user_cs_font_size*2)))\nlang.set(qn(\'w:bidi\'),\'fa-IR\')\nrFonts.set(qn(\'w:cs\'),user_cs_font_name)\n
Run Code Online (Sandbox Code Playgroud)\n