En este nuevo capítulo vamos a hablar sobre como configurar un poco el formato de nuestro texto en nuestro documento. Los bloques fundamentales de código son los párrafos, los cuales acumulan texto de izquierda a derecha y cuando se llega a un tope siguen acumulando texto de izquierda a derecha en la siguiente línea. Este tope depende del contenedor donde se encuentre el párrafo. El contenedor más natural y el que hemos estado usando hasta ahora principalmente es la página pero también podemos tener texto en columnas si decidimos dividir la página en más de una columna, podemos tener texto en la celda de una tabla, etc.
Vamos a empezar, seguimos el ritual de lo habitual, e importamos algunas bibliotecas:
from urllib.request import urlretrieve
import random
from docx import Document
from docx.shared import Cm, Pt, RGBColor
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
Creamos una nueva instancia de Document
:
doc = Document()
A este documento vacio le vamos a añadir un párrafo vacio:
par = doc.add_paragraph()
print(len(doc.paragraphs))
Veamos las cosas disponibles que tenemos en el párrafo:
print(dir(par))
El resultado de lo anterior mostrará:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', '_element',
'_insert_paragraph_before', '_p', '_parent', 'add_run', 'alignment',
'clear', 'insert_paragraph_before', 'paragraph_format', 'part',
'runs', 'style', 'text']
Trabajando con run‘s
Como véis, tenemos un método que se llama add_run
y una property que se llama runs
. El primero sirve para añadir pequeñas porciones de texto mientras que el segundo nos devuelve una lista con los diferentes run‘s que tiene el párrafo:
print(par.runs)
Lo anterior nos mostrará una lista vacía. De momento, no tenemos ningún run por lo que vamos a añadir uno.
par.add_run("El perro de ")
print(par.runs)
Lo anterior mostrará algo parecido a:
[<docx.text.run.Run object at 0x7f7c0132e950>]
Vemos que ahora tenemos un run dentro del párrafo. Vamos a añadir un segundo run:
par.add_run("San Roque")
print(par.runs)
Lo anterior mostrará:
[<docx.text.run.Run object at 0x7f7c012c0250>,
<docx.text.run.Run object at 0x7f7c012c0290>]
Añadimos un tercero, un cuarto y un quinto:
par.add_run(" no tiene rabo porque ")
par.add_run("Ramón Ramírez")
par.add_run(" se lo ha cortado.")
Podemos acceder a todo el texto contenido en el párrafo usando text
:
print(par.text)
Y lo anterior mostrará:
El perro de San Roque no tiene rabo porque Ramón Ramírez se lo ha cortado.
Accedemos al primer run:
r1 = par.runs[0]
Y vemos todo lo que nos ofrece:
print(dir(r1))
Lo anterior mostrará en pantalla:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', '_element', '_parent', '_r',
'add_break', 'add_picture', 'add_tab', 'add_text', 'bold', 'clear',
'element', 'font', 'italic', 'part', 'style', 'text', 'underline']
Podemos ver que dentro de un run podemos añadir texto (add_text
) pero también podemos añadir imágenes (add_picture
), tabulaciones (add_tab
). Vemos que podemos acceder al texto usando text
. Podemos limpiar el contenido usando clear
. Podemos hacer que el texto se vea en negrita (bold
), en cursiva (italic
),… Vamos a toquetear algunas de estas cosas usando un nuevo run.
r6 = par.add_run()
print(r6.text)
Vemos que no hay texto, de momento.
Vamos a descargar unas cuantas imágenes para añadirlas en un run:
urls = [
"https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/emojipedia/181/pinching-hand_1f90f.png",
"https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/emojipedia/181/orangutan_1f9a7.png",
"https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/emojipedia/181/ringed-planet_1fa90.png"
]
names = [url.split("/")[-1] for url in urls]
for url, name in zip(urls, names):
urlretrieve(url, filename=name)
A r6
le vamos a añadir texto e imágenes:
r6.add_text("Tienes el cerebro así ")
r6.add_picture(names[0], height=Cm(1), width=Cm(1))
r6.add_text(", como el de un ")
r6.add_picture(names[1], height=Cm(1), width=Cm(1))
r6.add_text(". Por mi, te puedes ir a ")
r6.add_picture(names[2], height=Cm(1), width=Cm(1))
r6.add_text(".")
[Inciso: estamos usando Cm
. Su uso parece obvio pero dejamos el entrar más en detalle para más adelante].
Si guardamos el documento y lo abrimos en un procesador de texto:
doc.save("new.docx")
Deberíamos ver algo parecido a:
Pero teníamos más runs y hemos visto que teníamos cosas como bold
o italic
. Vamos a hacer uso de ellas.
r2 = par.runs[1] # San Roque
r4 = par.runs[3] # Ramón Ramírez
r2
lo vamos a dejar en negrita y r4
en cursiva:
r2.bold = True
r4.italic = True
Si volvemos a guardar el documento y lo abrimos nuevamente con un procesador de textos:
doc.save("new.docx")
Deberíamos ver algo parecido a:
Vamos a añadir un nuevo párrafo:
par2 = doc.add_paragraph(
"El perro de Ramón Ramírez no tiene rabo "
"porque se lo han robado. "
"¿Quién le ha robado el rabo al perro de San Roque?. "
"¿Ramón Ramírez ha robado el rabo "
"del perro de San Roque?."
)
Vamos a acceder ahora al objeto ParagraphFormat
y ver lo que podemos hacer con ello:
par2_fmt = par2.paragraph_format
print(type(par2_fmt))
Lo anterior mostrará en pantalla:
<class 'docx.text.parfmt.ParagraphFormat'>
Veamos lo que nos ofrece:
print(dir(par2_fmt))
Lo anterior mostrará:
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__slots__', '__str__'
, '__subclasshook__', '_element', '_line_spacing',
'_line_spacing_rule', '_parent', '_tab_stops', 'alignment',
'element', 'first_line_indent', 'keep_together', 'keep_with_next',
'left_indent', 'line_spacing', 'line_spacing_rule',
'page_break_before', 'part', 'right_indent', 'space_after',
'space_before', 'tab_stops', 'widow_control']
Vemos muchas cositas. Si usáis procesadores de textos muchas os sonarán. Vamos a ver algunas de ellas pero antes vamos a guardar el documento para ver el estado actual del segundo párrafo antes de que empecemos a toquetear.
doc.save("new.docx")
Lo dicho, vamos a empezar a toquetear:
par2_fmt.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY
par2_fmt.first_line_indent = Cm(2)
[Inciso: estamos usando WD_PARAGRAPH_ALIGNMENT
. Su uso parece obvio pero entraremos más en detalle más adelante].
Lo que hemos hecho ahora es indicar que el texto del párrafo está justificado, es decir, que ocupa todo el ancho disponible del contenedor que estemos usando. Por otro lado hemos dicho que la primera línea tenga una indentación de 2 centímetros. Si guardamos y visualizamos:
doc.save("new.docx")
Deberíamos ver un documento parecido al siguiente:
Seguimos con un tercer párrafo con el que vamos a ver el objeto Font
. Podremos acceder al mismo desde un run. Veamos un ejemplo:
txt = ("Debajo de aquella pequeña peña, "
"hay otra peña más pequeña que la "
"peña pequeña, que había encima de "
"la peña más pequeña.")
font_names = ["Calibri", "Comic Sans MS", "Arial", "Consolas"]
sizes = (Pt(10), Pt(14), Pt(20))
# creamos un nuevo párrafo
par3 = doc.add_paragraph()
for word in txt.split(): # Para cada palabra (y posible signo de puntuación)
# Le creamos un run
r = par3.add_run(word)
# Accedemos a su objeto Font
font = r.font
# Le modificamos el color
r, g, b = random.choices(range(0,255), k=3)
color = RGBColor(r, g, b)
font.color.rgb = color
# Le modificamos la fuente
font_name = random.choice(font_names)
font.name = font_name
# Le modificamos el tamaño de la fuente
size = random.choice(sizes)
font.size = size
[Inciso: como anteriormente, más adelante daremos más detalles de RGBColor
y de Pt
. De todas formas, su uso parece muy intuitivo].
En el anterior código hemos metido palabras a las cuales les hemos modificado el tipo de fuente, tamaño y color. Vamos a guardar el documento y a visualizarlo:
doc.save("new.docx")
El resultado será algo parecido a:
Volvemos al objeto Font
y lo vamos a inspeccionar un poco para ver de qué más cosas dispone:
print(dir(font))
Lo anterior mostrará:
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__slots__', '__str__',
'__subclasshook__', '_element', '_get_bool_prop', '_parent',
'_set_bool_prop', 'all_caps', 'bold', 'color', 'complex_script',
'cs_bold', 'cs_italic', 'double_strike', 'element', 'emboss',
'hidden', 'highlight_color', 'imprint', 'italic', 'math', 'name',
'no_proof', 'outline', 'part', 'rtl', 'shadow', 'size', 'small_caps',
'snap_to_grid', 'spec_vanish', 'strike', 'subscript', 'superscript',
'underline', 'web_hidden']
Podemos crear subíndices y superíndices (subscript
, superscript
, respectivamente), poner todo el run en mayúsculas (all_caps
),…
Para ir terminando, vamos a crear un cuarto párrafo para ver una cosa en la que profundizaremos en el siguiente capítulo del tutorial:
par4 = doc.add_paragraph(
"Puede pedir Pepe perfectamente, "
"por presumido, pelo prestado, "
"pudiendo presumir por peinado, "
"por ponerse perifollos, propiamente. "
"Pero, para pedir pelo prestado, "
"pardiez, precisamente, "
"Pepe parésceme predestinado "
"para pagar, pobre pelado, "
"por presumido, pardiez, "
"por pedir pelo prestado.",
style="List Bullet"
)
Veis que he introducido una nueva keyword, style
. De momento dejamos el misterio aquí y seguiremos en el próximo capítulo. Vamos a guardar el documento y a ver cómo queda este último párrafo:
doc.save("new.docx")
El resultado es:
Resumen
Hemos visto que podemos bajar muchos niveles para ir haciendo cosas más detalladas. Si nos quedamos con el párrafo para muchos casos será más que suficiente pero si necesitamos formatear nuestro texto un poco más vemos que tenemos opciones y lo podríamos hacer incluso letra a letra.
Seguid atentos puesto que seguiremos desvelando cosas sobre los estilos y sobre esas nombres raros que hemos visto como Cm
, WD_PARAGRAPH_ALIGNMENT
,…