Answers.Body: <p><a href="https://github.com/moshi4/pyMSAviz" rel="nofollow noreferrer">pyMSAviz</a> may help with this problem.
  pyMSAviz is a Python tool with a convenient CLI/API implementation for visualizing MSA files.</p>
  <p>For example, you can output MSA figure in PDF format with the following command.</p>
  <pre><code>pymsaviz -i Multi.txt -o msa_result.pdf --format clustal \
           --wrap_length 60 --show_count --color_scheme Identity
  </code></pre>
  <p><a href="https://i.sstatic.net/xTbPh.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/xTbPh.png" alt="msa_result.png" /></a></p>
  <p>You can also use pyMSAviz's API to configure the appearance of the figure in more detail, such as highlighting.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-12-21T14:05:51.040Z
Answers.Text: Thanks for your solution Moshi. I personally think @pippo1980's solution is cooler because they leverage AlignIO within the StringIO (or StringIO within AlignIO). This permits a lot of flexibility in possible solutions because we use BioPython (AlignIO) all the time and is very cool OO.
Answers.UserDisplayName: M__
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-12-22T00:12:18.413Z
Answers.Text: personally I think your tool is supercool, spent last 3 days googling for such thing, but wasnt able to find anything ( I mean espript3 is supersupercool like jalview but no python API ... could be wrong). Wasted a day trying to understand how to get PyMuPDF  to find words, like 'ggaag' in my out.pdf with no luck. I am intrigued by your tool. Is there a way to color conserved residues all blue and non conserved like red ?? Was thinking about how to realize such a thing, Pyfpdf2 can highligh single cell and I should find a way to print each single alignment letter in a single cell and devising
Answers.UserDisplayName: pippo1980
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-12-22T00:13:32.627Z
Answers.Text: a strategy to do that. Sorry I cannot upvote more than once.
Answers.UserDisplayName: pippo1980
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-12-22T00:34:31.563Z
Answers.Text: yep found  --color_scheme Identity in your examples !!!
Answers.UserDisplayName: pippo1980
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-12-24T01:02:16.517Z
Answers.Text: Hi @pippo1980. I'm pretty sure you could get yours working. Note that MEGA X alignment editor will colour conserved residues (but you don't get to choose the colour) and can't do non-conserved residues. Merry Christmas!
Answers.UserDisplayName: M__
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-12-21T10:20:09.240Z
Answers.LastActivityDate: 2022-12-21T10:20:09.240Z
Answers.UserDisplayName: moshi
Answers.Body: <p>adding, a second answer because the OP request about</p>
  <blockquote>
  <p>I need to import the file and then highlight some specific word</p>
  </blockquote>
  <p>was making me uneasy. I kept using the <code>Pyfpdf2</code> library and made use of the Biopython <code>SeqRecord.letter_annotations</code> attribute to track Nucleotides index composing the words found by RegEx. Tried to use the <code>page.search_for()</code> of the <code>PyMuPDF</code> library directly on the output pdf of my previous answer but was not getting word accross line breaks [if anybody know better, please help].</p>
  <p>Here my code, it's a two half problem, first find the word (in my example is '<em>atat</em>'), then write everything to pdf, I stole it from <a href="https://github.com/biopython/biopython/blob/8d9d20d09c6ee2a672c8477e890f0ab4e9e48825/Bio/AlignIO/ClustalIO.py#L22" rel="nofollow noreferrer">from /master/Bio/AlignIO/ClustalIO.py/   def write_alignment(self, alignment): from class ClustalWriter(SequentialAlignmentWriter)</a> with some modding [I believe but not absolutely sure that ...A cell by definition is uniform in font and color... ]:</p>
  <pre><code>import re
  
  from Bio import AlignIO  # Biopython 1.80
  
  from fpdf import FPDF # pip install fpdf2
   
  
  alignment = AlignIO.read(&quot;Multi.txt&quot;, &quot;clustal&quot;)
  
  
  word_to_find = 'atat'
  
  for rec in alignment:
      
      rec.letter_annotations['highlight'] = 'N'*len(rec.seq)
  
  # for rec in alignment:    
  #     print('rec.id : ', rec.id, 'highlight : ', rec.letter_annotations['highlight'])
      
      
  for rec in alignment:
      
      # print(str(rec.seq))
      
      found = [m.start() for m in re.finditer( '(?='+re.escape(word_to_find)+')' , re.escape(str(rec.seq.replace('-', 'Z'))))]
      
      # print(rec.id , ' found : ', found)
      
      for i in found :
          
              a = list(rec.letter_annotations['highlight'])
      
              a[i:i+len(word_to_find)] = 'Y'*len(word_to_find)
              
              rec.letter_annotations['highlight'] = ''.join(i for i in a)
              
              # print(rec.letter_annotations['highlight'])
              
              
  
  max_length = alignment.get_alignment_length()
  
  
  # stolen from /master/Bio/AlignIO/ClustalIO.py/   def write_alignment(self, alignment): from class ClustalWriter(SequentialAlignmentWriter):
  def write_alignment(alignment):
      
          x = 2
          
          y = 2
  
          pdf = FPDF(orientation=&quot;P&quot;, unit=&quot;mm&quot;, format=&quot;A4&quot;)
           
          # Add a page
          pdf.add_page()
  
          pdf.add_font('FreeMono', '', 'FreeMono.ttf')
  
          pdf.set_font(&quot;FreeMono&quot;, size = 12)
          
          pdf.set_fill_color(255, 255, 0)
  
      
          &quot;&quot;&quot;Use this to write (another) single alignment to an open file.&quot;&quot;&quot;
          if len(alignment) == 0:
              raise ValueError(&quot;Must have at least one sequence&quot;)
          if alignment.get_alignment_length() == 0:
              # This doubles as a check for an alignment object
              raise ValueError(&quot;Non-empty sequences are required&quot;)
  
          # Old versions of the parser in Bio.Clustalw used a ._version property
          try:
              version = str(alignment._version)
          except AttributeError:
              version = &quot;&quot;
          if not version:
              version = &quot;1.81&quot;
          if version.startswith(&quot;2.&quot;):
              # e.g. 2.0.x
              output = f&quot;CLUSTAL {version} multiple sequence alignment&quot;
          else:
              # e.g. 1.81 or 1.83
              output = f&quot;CLUSTAL X ({version}) multiple sequence alignment&quot;
              
          pdf.cell(0, y, txt = output , border = 0,  new_x='LMARGIN'  , new_y=&quot;NEXT&quot; ,  align = 'L', fill = False)
          pdf.cell(0, 4*y, txt = '' , border = 0,  new_x='LMARGIN'  , new_y=&quot;NEXT&quot; ,  align = 'L', fill = False)
          
  
          cur_char = 0
          # # max_length = len(alignment[0])
          max_length = alignment.get_alignment_length()
  
  
          if max_length &lt;= 0:
              raise ValueError(&quot;Non-empty sequences are required&quot;)
  
          if &quot;clustal_consensus&quot; in alignment.column_annotations:
              star_info = alignment.column_annotations[&quot;clustal_consensus&quot;]
          else:
              try:
                  # This was originally stored by Bio.Clustalw as ._star_info
                  star_info = alignment._star_info
              except AttributeError:
                  star_info = None
  
          # keep displaying sequences until we reach the end
          
          rec_cnt = 0
          
          while cur_char != max_length:
              # calculate the number of sequences to show, which will
              # be less if we are at the end of the sequence
              if (cur_char + 50) &gt; max_length:
                  show_num = max_length - cur_char
              else:
                  show_num = 50
  
              # go through all of the records and print out the sequences
              # when we output, we do a nice 80 column output, although this
              # may result in truncation of the ids.
              for record in alignment:
                  
                  rec_cnt += 1 
                  
                  # Make sure we don't get any spaces in the record
                  # identifier when output in the file by replacing
                  # them with underscores:
                  line = record.id[0:30].replace(&quot; &quot;, &quot;_&quot;).ljust(50)
                  
                  pdf.cell(50, y, txt = line, border = 0,  new_x='RIGHT' , align = 'L', fill = False)
  
                  
                  line_seq = list(str(record.seq[cur_char : (cur_char + show_num)]))
                  
                  line_seq_highlight = list(str(record.letter_annotations['highlight'][cur_char : (cur_char + show_num)]))
                  
                  
                  for i in range(len(line_seq)):
                      
                      if line_seq_highlight[i] == 'N' :
                          
                          filling = False
                      
                      if line_seq_highlight[i] == 'Y' :
                          
                          filling = True
                          
                      else:
                          
                          filling = False
                          
                      pdf.cell(x, y, txt = line_seq[i], border = 0,  new_x= 'RIGHT' ,  align = 'C', fill = filling)
                      
                  pdf.cell(x, 2*y, txt = ' ' , border = 0,  new_x=&quot;LMARGIN&quot; , new_y=&quot;NEXT&quot; ,  align = 'L', fill = False)
                  
                  # now we need to print out the star info, if we've got it
                  
                  if star_info and rec_cnt == len(alignment):
                      
                      pdf.cell(x, 2*y, txt = ' ' , border = 0,  new_x=&quot;LMARGIN&quot; , new_y=&quot;NEXT&quot; ,  align = 'L', fill = False)
                      
                      rec_cnt = 0
                      
                      # print(star_info)
                      
                      line = (' ' * 50)
                      
                      pdf.cell(50, y, txt = line, border = 0,  new_x='RIGHT' , align = 'L', fill = False)
                      
                      star_info_seq = list(str(star_info[cur_char : (cur_char + show_num)]))
  
                      for i in range(len(star_info_seq)):
                          
                          pdf.cell(x, y, txt = star_info_seq[i], border = 0,  new_x= 'RIGHT' ,  align = 'C', fill = False)
  
  
                      
                  pdf.cell(x, 2*y, txt = ' ' , border = 0,  new_x=&quot;LMARGIN&quot; , new_y=&quot;NEXT&quot; ,  align = 'L', fill = False)
                  
              
                  
              cur_char += show_num  
              
              pdf.cell(x, 3*y, txt = ' ' , border = 0,  new_x=&quot;LMARGIN&quot; , new_y=&quot;NEXT&quot; ,  align = 'L', fill = False)
  
  
          
          pdf.output(&quot;out.pdf&quot;) 
  
  
  write_alignment(alignment)
  
  
  </code></pre>
  <p>I tested it, with the <code>Multi.txt</code> Clustal alignment from above, output is</p>
  <p><code>out.pdf</code> :</p>
  <p><a href="https://i.sstatic.net/h6sMB.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/h6sMB.png" alt="enter image description here" /></a></p>
  <p>.</p>
  <p>One last note:</p>
  <p>needed to replace <code>-</code> with <code>Z</code> in the Consensus string to be able to get reliable results from the RegEX line : <code>found = [m.start() for m in re.finditer( '(?='+re.escape(word_to_find)+')' , re.escape(str(rec.seq.replace('-', 'Z'))))]</code> , not sure what is going on here, got the snippet from a SO post.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-12-24T00:33:59.087Z
Answers.LastActivityDate: 2022-12-24T00:47:27.317Z
Answers.LastEditDate: 2022-12-24T00:47:27.317Z
Answers.UserDisplayName: pippo1980
Answers.Body: <p>Ok, figured out a way, not sure its the best one,</p>
  <p>nedd to install <code>fpdf2</code> (<code>pip install fpdf2</code>)</p>
  <pre><code>from io import StringIO
  
  from Bio import AlignIO  # Biopython 1.80
  
  from fpdf import FPDF # pip install fpdf2
   
  alignment = AlignIO.read(&quot;Multi.txt&quot;, &quot;clustal&quot;)
  
  
  stri = StringIO()
      
  AlignIO.write(alignment, stri, 'clustal' )
  
  # print(stri.getvalue())
  
  stri_lines = [ i for i in stri.getvalue().split('\n')]
  
  # print(stri_lines)
  
  pdf = FPDF(orientation=&quot;P&quot;, unit=&quot;mm&quot;, format=&quot;A4&quot;)
   
  # Add a page
  pdf.add_page()
  
  pdf.add_font('FreeMono', '', 'FreeMono.ttf')
  
  pdf.set_font(&quot;FreeMono&quot;, size = 8)
  
  for x in stri_lines:
      pdf.cell(0, 5, txt = x, border = 0,  new_x=&quot;LMARGIN&quot; , new_y=&quot;NEXT&quot;,  align = 'L', fill = False)
      
      # print(len(x))
  
  pdf.output(&quot;out.pdf&quot;) 
  </code></pre>
  <p>output pdf  <code>out.pdf</code> :</p>
  <p><a href="https://i.sstatic.net/oDg7V.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/oDg7V.png" alt="enter image description here" /></a></p>
  <p>Not sure why the file Header is changed, think is something within Biopythion (!!! ???) you can check adding:</p>
  <pre><code>with open('file_output.txt', 'w') as filehandler:
      AlignIO.write(alignment, filehandler, 'clustal')
  </code></pre>
  <p>I had to place a tff FreeMono font (Mono spaced font in my script directory) see: <code>pdf.add_font('FreeMono', '', 'FreeMono.ttf')</code> otherwise the alignement won't be printed in the correct way
  <a href="https://graphicdesign.stackexchange.com/questions/13148/which-fonts-have-the-same-width-for-every-character">Which fonts have the same width for every character?</a>.</p>
  <p>Attached a png of my pdf. See that you can highlight it,</p>
  <p>using:</p>
  <pre><code>pdf.set_fill_color(255, 255, 0)
  
  filling = False
  for x in stri_lines:
      if 'Consensus' in x:
          filling = True
      else:
          filling = False
      pdf.cell(0, 5, txt = x, border = 0,  new_x=&quot;LMARGIN&quot; , new_y=&quot;NEXT&quot;,  align = 'L', fill = filling)
  </code></pre>
  <p>or something similar you can highlight while printing:</p>
  <p><a href="https://i.sstatic.net/9Dlzn.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/9Dlzn.png" alt="enter image description here" /></a></p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-12-20T11:10:16.550Z
Answers.LastActivityDate: 2022-12-20T21:57:50.623Z
Answers.LastEditDate: 2022-12-20T21:57:50.623Z
Answers.UserDisplayName: pippo1980
Body: <p><em>This question was also asked on <a href="https://stackoverflow.com/questions/74852238/read-clustal-file-in-python">StackOverflow</a></em></p>
  <p>I have a multiple sequence alignment (MSA) file derived from mafft in clustal format which I want to import into Python and save into a PDF file. I need to import the file and then highlight some specific words. I've tried to simply import the pdf of the MSA but after the highlight command doesn't work.</p>
  <p>I need to print the file like this:
  <a href="https://i.sstatic.net/OKNW2.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/OKNW2.png" alt="enter image description here" /></a></p>
  <p><code>Multi.txt</code> :</p>
  <pre><code>CLUSTAL format alignment by MAFFT FFT-NS-i (v7.453)
  
  
  Consensus       --------------------------------------acgttttcgatatttatgccat
  AMP             tttatattttctcctttttatgatggaacaagtctgcgacgttttcgatatttatgccat
                                                        **********************
  
  Consensus       atgtgcatgttgtaaggttgaaagcaaaaatgaggggaaaaaaaatgaggtttttaataa
  AMP             atgtgcatgttgtaaggttgaaagcaaaaatgaggggaaaaaaaatgaggtttttaataa
                  ************************************************************
  
  Consensus       ctacacatttagaggtctaggaaataaaggagtattaccatggaaatgtatttccctaga
  AMP             ctacacatttagaggtctaggaaataaaggagtattaccatggaaatgtaattccctaga
                  ************************************************** *********
  
  Consensus       tatgaaatattttcgtgcagttacaacatatgtgaatgaatcaaaatatgaaaaattgaa
  AMP             tatgaaatattttcgtgcagttacaacatatgtgaatgaatcaaaatatgaaaaattgaa
                  ************************************************************
  
  Consensus       atataagagatgtaaatatttaaacaaagaaactgtggataatgtaaatgatatgcctaa
  AMP             atataagagatgtaaatatttaaacaaagaaactgtggataatgtaaatgatatgcctaa
                  ************************************************************
  
  Consensus       ttctaaaaaattacaaaatgttgtagttatgggaagaacaaactgggaaagcattccaaa
  AMP             ttctaaaaaattacaaaatgttgtagttatgggaagaacaaactgggaaagcattccaaa
                  ************************************************************
  
  Consensus       aaaatttaaacctttaagcaataggataaatgttatattgtctagaaccttaaaaaaaga
  AMP             aaaatttaaacctttaagcaataggataaatgttatattgtctagaaccttaaaaaaaga
                  ************************************************************
  
  Consensus       agattttgatgaagatgtttatatcattaacaaagttgaagatctaatagttttacttgg
  AMP             agattttgatgaagatgtttatatcattaacaaagttgaagatctaatagttttacttgg
                  ************************************************************
  
  Consensus       gaaattaaattactataaatgttttattataggaggttccgttgtttatcaagaattttt
  AMP             gaaattaaattactataaatgttttattataggaggttccgttgtttatcaagaattttt
                  ************************************************************
  
  Consensus       agaaaagaaattaataaaaaaaatatattttactagaataaatagtacatatgaatgtga
  AMP             agaaaagaaattaataaaaaaaatatattttactagaataaatagtacatatgaatgtga
                  ************************************************************
  
  Consensus       tgtattttttccagaaataaatgaaaatgagtatcaaattatttctgttagcgatgtata
  AMP             tgtattttttccagaaataaatgaaaatgagtatcaaattatttctgttagcgatgtata
                  ************************************************************
  
  Consensus       tactagtaacaatacaacattgga----------------------------------
  AMP             tactagtaacaatacaacattggattttatcatttataagaaaacgaataataaaatg
                  ************************                                  
  </code></pre>
  <p>How can I import the alignment and print in the new PDF with the right alignment of the sequences.</p>
  <p>Thanks</p>
  
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2022-12-20T08:58:01.223Z
Comments.Text: Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking.
Comments.UserDisplayName: Community
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2022-12-20T09:01:46.460Z
Comments.Text: What words do you need to highlight?
Comments.UserDisplayName: M__
Tags: <python><phylogenetics><multiple-sequence-alignment>
Title: Read Clustal file in Python
ThreadText: # Read Clustal file in Python
  
  (Asked by: username_0 on 2022-12-20)
  
  *This question was also asked on [StackOverflow](https://stackoverflow.com/questions/74852238/read-clustal-file-in-python)*
  
  I have a multiple sequence alignment (MSA) file derived from mafft in clustal format which I want to import into Python and save into a PDF file. I need to import the file and then highlight some specific words. I've tried to simply import the pdf of the MSA but after the highlight command doesn't work.
  
  I need to print the file like this:
  [![enter image description here](https://i.sstatic.net/OKNW2.png)](https://i.sstatic.net/OKNW2.png)
  
  `Multi.txt` :
  
  ```
  CLUSTAL format alignment by MAFFT FFT-NS-i (v7.453)
  
  Consensus       --------------------------------------acgttttcgatatttatgccat
  AMP             tttatattttctcctttttatgatggaacaagtctgcgacgttttcgatatttatgccat
                                                        **********************
  
  Consensus       atgtgcatgttgtaaggttgaaagcaaaaatgaggggaaaaaaaatgaggtttttaataa
  AMP             atgtgcatgttgtaaggttgaaagcaaaaatgaggggaaaaaaaatgaggtttttaataa
                  ************************************************************
  
  Consensus       ctacacatttagaggtctaggaaataaaggagtattaccatggaaatgtatttccctaga
  AMP             ctacacatttagaggtctaggaaataaaggagtattaccatggaaatgtaattccctaga
                  ************************************************** *********
  
  Consensus       tatgaaatattttcgtgcagttacaacatatgtgaatgaatcaaaatatgaaaaattgaa
  AMP             tatgaaatattttcgtgcagttacaacatatgtgaatgaatcaaaatatgaaaaattgaa
                  ************************************************************
  
  Consensus       atataagagatgtaaatatttaaacaaagaaactgtggataatgtaaatgatatgcctaa
  AMP             atataagagatgtaaatatttaaacaaagaaactgtggataatgtaaatgatatgcctaa
                  ************************************************************
  
  Consensus       ttctaaaaaattacaaaatgttgtagttatgggaagaacaaactgggaaagcattccaaa
  AMP             ttctaaaaaattacaaaatgttgtagttatgggaagaacaaactgggaaagcattccaaa
                  ************************************************************
  
  Consensus       aaaatttaaacctttaagcaataggataaatgttatattgtctagaaccttaaaaaaaga
  AMP             aaaatttaaacctttaagcaataggataaatgttatattgtctagaaccttaaaaaaaga
                  ************************************************************
  
  Consensus       agattttgatgaagatgtttatatcattaacaaagttgaagatctaatagttttacttgg
  AMP             agattttgatgaagatgtttatatcattaacaaagttgaagatctaatagttttacttgg
                  ************************************************************
  
  Consensus       gaaattaaattactataaatgttttattataggaggttccgttgtttatcaagaattttt
  AMP             gaaattaaattactataaatgttttattataggaggttccgttgtttatcaagaattttt
                  ************************************************************
  
  Consensus       agaaaagaaattaataaaaaaaatatattttactagaataaatagtacatatgaatgtga
  AMP             agaaaagaaattaataaaaaaaatatattttactagaataaatagtacatatgaatgtga
                  ************************************************************
  
  Consensus       tgtattttttccagaaataaatgaaaatgagtatcaaattatttctgttagcgatgtata
  AMP             tgtattttttccagaaataaatgaaaatgagtatcaaattatttctgttagcgatgtata
                  ************************************************************
  
  Consensus       tactagtaacaatacaacattgga----------------------------------
  AMP             tactagtaacaatacaacattggattttatcatttataagaaaacgaataataaaatg
                  ************************                                  
  
  ```
  
  How can I import the alignment and print in the new PDF with the right alignment of the sequences.
  
  Thanks
  
  **Tags:** python, phylogenetics, multiple-sequence-alignment
  
  > **Comments:**
  > **username_1**: Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking.  
  > **username_2**: What words do you need to highlight?  
  
  ---
  
  ## 3 answers
  
  ### Answer by username_4 on 2022-12-20. Score: 2 | Accepted answer
  
  Ok, figured out a way, not sure its the best one,
  
  nedd to install `fpdf2` (`pip install fpdf2`)
  
  ```
  from io import StringIO
  
  from Bio import AlignIO  # Biopython 1.80
  
  from fpdf import FPDF # pip install fpdf2
   
  alignment = AlignIO.read("Multi.txt", "clustal")
  
  stri = StringIO()
      
  AlignIO.write(alignment, stri, 'clustal' )
  
  # print(stri.getvalue())
  
  stri_lines = [ i for i in stri.getvalue().split('\n')]
  
  # print(stri_lines)
  
  pdf = FPDF(orientation="P", unit="mm", format="A4")
   
  # Add a page
  pdf.add_page()
  
  pdf.add_font('FreeMono', '', 'FreeMono.ttf')
  
  pdf.set_font("FreeMono", size = 8)
  
  for x in stri_lines:
      pdf.cell(0, 5, txt = x, border = 0,  new_x="LMARGIN" , new_y="NEXT",  align = 'L', fill = False)
      
      # print(len(x))
  
  pdf.output("out.pdf") 
  
  ```
  
  output pdf `out.pdf` :
  
  [![enter image description here](https://i.sstatic.net/oDg7V.png)](https://i.sstatic.net/oDg7V.png)
  
  Not sure why the file Header is changed, think is something within Biopythion (!!! ???) you can check adding:
  
  ```
  with open('file_output.txt', 'w') as filehandler:
      AlignIO.write(alignment, filehandler, 'clustal')
  
  ```
  
  I had to place a tff FreeMono font (Mono spaced font in my script directory) see: `pdf.add_font('FreeMono', '', 'FreeMono.ttf')` otherwise the alignement won't be printed in the correct way
  [Which fonts have the same width for every character?](https://graphicdesign.stackexchange.com/questions/13148/which-fonts-have-the-same-width-for-every-character).
  
  Attached a png of my pdf. See that you can highlight it,
  
  using:
  
  ```
  pdf.set_fill_color(255, 255, 0)
  
  filling = False
  for x in stri_lines:
      if 'Consensus' in x:
          filling = True
      else:
          filling = False
      pdf.cell(0, 5, txt = x, border = 0,  new_x="LMARGIN" , new_y="NEXT",  align = 'L', fill = filling)
  
  ```
  
  or something similar you can highlight while printing:
  
  [![enter image description here](https://i.sstatic.net/9Dlzn.png)](https://i.sstatic.net/9Dlzn.png)
  
  
  ---
  
  ### Answer by username_3 on 2022-12-21. Score: 3
  
  [pyMSAviz](https://github.com/moshi4/pyMSAviz) may help with this problem.
  pyMSAviz is a Python tool with a convenient CLI/API implementation for visualizing MSA files.
  
  For example, you can output MSA figure in PDF format with the following command.
  
  ```
  pymsaviz -i Multi.txt -o msa_result.pdf --format clustal \
           --wrap_length 60 --show_count --color_scheme Identity
  
  ```
  
  [![msa_result.png](https://i.sstatic.net/xTbPh.png)](https://i.sstatic.net/xTbPh.png)
  
  You can also use pyMSAviz's API to configure the appearance of the figure in more detail, such as highlighting.
  
  > **Comments:**
  > **username_2**: Thanks for your solution username_3. I personally think @username_4's solution is cooler because they leverage AlignIO within the StringIO (or StringIO within AlignIO). This permits a lot of flexibility in possible solutions because we use BioPython (AlignIO) all the time and is very cool OO.  
  > **username_4**: personally I think your tool is supercool, spent last 3 days googling for such thing, but wasnt able to find anything ( I mean espript3 is supersupercool like jalview but no python API ... could be wrong). Wasted a day trying to understand how to get PyMuPDF  to find words, like 'ggaag' in my out.pdf with no luck. I am intrigued by your tool. Is there a way to color conserved residues all blue and non conserved like red ?? Was thinking about how to realize such a thing, Pyfpdf2 can highligh single cell and I should find a way to print each single alignment letter in a single cell and devising  
  > **username_4**: a strategy to do that. Sorry I cannot upvote more than once.  
  > **username_4**: yep found  --color_scheme Identity in your examples !!!  
  > **username_2**: Hi @username_4. I'm pretty sure you could get yours working. Note that MEGA X alignment editor will colour conserved residues (but you don't get to choose the colour) and can't do non-conserved residues. Merry Christmas!  
  
  ---
  
  ### Answer by username_4 on 2022-12-24. Score: 2
  
  adding, a second answer because the OP request about
  
  > I need to import the file and then highlight some specific word
  
  was making me uneasy. I kept using the `Pyfpdf2` library and made use of the Biopython `SeqRecord.letter_annotations` attribute to track Nucleotides index composing the words found by RegEx. Tried to use the `page.search_for()` of the `PyMuPDF` library directly on the output pdf of my previous answer but was not getting word accross line breaks [if anybody know better, please help].
  
  Here my code, it's a two half problem, first find the word (in my example is '*atat*'), then write everything to pdf, I stole it from [from /master/Bio/AlignIO/ClustalIO.py/ def write\_alignment(self, alignment): from class ClustalWriter(SequentialAlignmentWriter)](https://github.com/biopython/biopython/blob/8d9d20d09c6ee2a672c8477e890f0ab4e9e48825/Bio/AlignIO/ClustalIO.py#L22) with some modding [I believe but not absolutely sure that ...A cell by definition is uniform in font and color... ]:
  
  ```
  import re
  
  from Bio import AlignIO  # Biopython 1.80
  
  from fpdf import FPDF # pip install fpdf2
   
  
  alignment = AlignIO.read("Multi.txt", "clustal")
  
  word_to_find = 'atat'
  
  for rec in alignment:
      
      rec.letter_annotations['highlight'] = 'N'*len(rec.seq)
  
  # for rec in alignment:    
  #     print('rec.id : ', rec.id, 'highlight : ', rec.letter_annotations['highlight'])
      
      
  for rec in alignment:
      
      # print(str(rec.seq))
      
      found = [m.start() for m in re.finditer( '(?='+re.escape(word_to_find)+')' , re.escape(str(rec.seq.replace('-', 'Z'))))]
      
      # print(rec.id , ' found : ', found)
      
      for i in found :
          
              a = list(rec.letter_annotations['highlight'])
      
              a[i:i+len(word_to_find)] = 'Y'*len(word_to_find)
              
              rec.letter_annotations['highlight'] = ''.join(i for i in a)
              
              # print(rec.letter_annotations['highlight'])
              
              
  
  max_length = alignment.get_alignment_length()
  
  # stolen from /master/Bio/AlignIO/ClustalIO.py/   def write_alignment(self, alignment): from class ClustalWriter(SequentialAlignmentWriter):
  def write_alignment(alignment):
      
          x = 2
          
          y = 2
  
          pdf = FPDF(orientation="P", unit="mm", format="A4")
           
          # Add a page
          pdf.add_page()
  
          pdf.add_font('FreeMono', '', 'FreeMono.ttf')
  
          pdf.set_font("FreeMono", size = 12)
          
          pdf.set_fill_color(255, 255, 0)
  
      
          """Use this to write (another) single alignment to an open file."""
          if len(alignment) == 0:
              raise ValueError("Must have at least one sequence")
          if alignment.get_alignment_length() == 0:
              # This doubles as a check for an alignment object
              raise ValueError("Non-empty sequences are required")
  
          # Old versions of the parser in Bio.Clustalw used a ._version property
          try:
              version = str(alignment._version)
          except AttributeError:
              version = ""
          if not version:
              version = "1.81"
          if version.startswith("2."):
              # e.g. 2.0.x
              output = f"CLUSTAL {version} multiple sequence alignment"
          else:
              # e.g. 1.81 or 1.83
              output = f"CLUSTAL X ({version}) multiple sequence alignment"
              
          pdf.cell(0, y, txt = output , border = 0,  new_x='LMARGIN'  , new_y="NEXT" ,  align = 'L', fill = False)
          pdf.cell(0, 4*y, txt = '' , border = 0,  new_x='LMARGIN'  , new_y="NEXT" ,  align = 'L', fill = False)
          
  
          cur_char = 0
          # # max_length = len(alignment[0])
          max_length = alignment.get_alignment_length()
  
          if max_length <= 0:
              raise ValueError("Non-empty sequences are required")
  
          if "clustal_consensus" in alignment.column_annotations:
              star_info = alignment.column_annotations["clustal_consensus"]
          else:
              try:
                  # This was originally stored by Bio.Clustalw as ._star_info
                  star_info = alignment._star_info
              except AttributeError:
                  star_info = None
  
          # keep displaying sequences until we reach the end
          
          rec_cnt = 0
          
          while cur_char != max_length:
              # calculate the number of sequences to show, which will
              # be less if we are at the end of the sequence
              if (cur_char + 50) > max_length:
                  show_num = max_length - cur_char
              else:
                  show_num = 50
  
              # go through all of the records and print out the sequences
              # when we output, we do a nice 80 column output, although this
              # may result in truncation of the ids.
              for record in alignment:
                  
                  rec_cnt += 1 
                  
                  # Make sure we don't get any spaces in the record
                  # identifier when output in the file by replacing
                  # them with underscores:
                  line = record.id[0:30].replace(" ", "_").ljust(50)
                  
                  pdf.cell(50, y, txt = line, border = 0,  new_x='RIGHT' , align = 'L', fill = False)
  
                  
                  line_seq = list(str(record.seq[cur_char : (cur_char + show_num)]))
                  
                  line_seq_highlight = list(str(record.letter_annotations['highlight'][cur_char : (cur_char + show_num)]))
                  
                  
                  for i in range(len(line_seq)):
                      
                      if line_seq_highlight[i] == 'N' :
                          
                          filling = False
                      
                      if line_seq_highlight[i] == 'Y' :
                          
                          filling = True
                          
                      else:
                          
                          filling = False
                          
                      pdf.cell(x, y, txt = line_seq[i], border = 0,  new_x= 'RIGHT' ,  align = 'C', fill = filling)
                      
                  pdf.cell(x, 2*y, txt = ' ' , border = 0,  new_x="LMARGIN" , new_y="NEXT" ,  align = 'L', fill = False)
                  
                  # now we need to print out the star info, if we've got it
                  
                  if star_info and rec_cnt == len(alignment):
                      
                      pdf.cell(x, 2*y, txt = ' ' , border = 0,  new_x="LMARGIN" , new_y="NEXT" ,  align = 'L', fill = False)
                      
                      rec_cnt = 0
                      
                      # print(star_info)
                      
                      line = (' ' * 50)
                      
                      pdf.cell(50, y, txt = line, border = 0,  new_x='RIGHT' , align = 'L', fill = False)
                      
                      star_info_seq = list(str(star_info[cur_char : (cur_char + show_num)]))
  
                      for i in range(len(star_info_seq)):
                          
                          pdf.cell(x, y, txt = star_info_seq[i], border = 0,  new_x= 'RIGHT' ,  align = 'C', fill = False)
  
                      
                  pdf.cell(x, 2*y, txt = ' ' , border = 0,  new_x="LMARGIN" , new_y="NEXT" ,  align = 'L', fill = False)
                  
              
                  
              cur_char += show_num  
              
              pdf.cell(x, 3*y, txt = ' ' , border = 0,  new_x="LMARGIN" , new_y="NEXT" ,  align = 'L', fill = False)
  
          
          pdf.output("out.pdf") 
  
  write_alignment(alignment)
  
  ```
  
  I tested it, with the `Multi.txt` Clustal alignment from above, output is
  
  `out.pdf` :
  
  [![enter image description here](https://i.sstatic.net/h6sMB.png)](https://i.sstatic.net/h6sMB.png)
  
  .
  
  One last note:
  
  needed to replace `-` with `Z` in the Consensus string to be able to get reliable results from the RegEX line : `found = [m.start() for m in re.finditer( '(?='+re.escape(word_to_find)+')' , re.escape(str(rec.seq.replace('-', 'Z'))))]` , not sure what is going on here, got the snippet from a SO post.
  
  
  ---
  
  