HTMLタグ内のカラーコードをPillowで適用する【Python】

# 初めに
これが
```
'<span style="color: #0000FF">これが青い</span>、またhogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge<span class="font-bold" style="color: #FEDCBA">別の色</span>、<span class="font-bold" style="color: #777777">これがグレー</span>、<span class="font-bold" style="color: #FFFF00">これが黄色い</span>、',
```

こうなります。
![スクリーンショット 2023-09-22 112153.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538774/94d73a4c-aa3a-95bb-b83b-da6348716779.png)

HTMLを処理して何かするときに使えます。

# 仕様
<span ~　から始まる部分を探して、その中身を指定された色で描画できます。divだったりclassで指定されてる場合は適宜置き換えてください。

例:
```
<div class="red_txt">hoge</div>
```
だった場合、cssなどから色を取得して置き換える
```
<span style="color: #FF0000">hoge</span>
```

# コード
以下がコードになります。
```python:main.py
def despan_draw(draw, font_file, text, font_size, starcord):
    x_pos = starcord[0]
    y_pos = starcord[1]
    pattern = re.compile(
        r'<span[^>]*style="color: #[a-zA-Z0-9].....">(.*?)<\/span>',
        re.DOTALL,
    )
    lines = text.split("\n")
    for line in lines:
        if "<span" in line:
            amount = line.count("<span")
            for i in range(amount):
                start_idx = line.find("<span")
                end_idx = line.find("</span>")
                if start_idx != -1 and end_idx != -1:
                    style = line[start_idx : end_idx + 7]  # '<span ... </span>' の部分
                    color_start_idx = style.find("color:")
                    if color_start_idx != -1:
                        color_hex = style[color_start_idx + 8 : color_start_idx + 14]
                        text_color = tuple(
                            int(color_hex[i : i + 2], 16) for i in (0, 2, 4)
                        )
                # マッチした部分を抽出
                match = pattern.search(line)
                if match:
                    x = x_pos
                    span_text = match.group(1)
                    line = pattern.sub(rf"{span_text}", line, count=1)
                    span_start = line.find(span_text)
                    ct = 0
                    for char in line:
                        if char == "<":
                            x_pos = x
                            break
                        line = line[1:]
                        if ct >= span_start and ct < span_start + len(span_text):
                            char_color = text_color
                        else:  # その他の部分
                            char_color = (255, 255, 255)
                        draw.text(
                            (x, y_pos),
                            char,
                            fill=char_color,
                            font=ImageFont.truetype(font_file, font_size),
                        )
                        char_bbox = draw.textbbox(
                            (x, y_pos),
                            char,
                            font=ImageFont.truetype(font_file, font_size),
                        )
                        char_width = char_bbox[2] - char_bbox[0]
                        x += char_width
                        ct += 1
                        # ここで改行もできる(適宜調整)
                        if x >= 2000:
                            x = starcord[0]
                            y_pos += font_size
        else:
            # 普通の文字列
            draw.text(
                (int(x_pos), int(y_pos)),
                line,
                fill=(255, 255, 255),
                font=ImageFont.truetype(font_file, font_size),
            )
        y_pos += font_size
        x_pos = starcord[0]
```
上記の関数を呼び出すことで描画されます。
```python:main.py
def draw_image(font_file):
    canvas = Image.new("RGBA", (2048, 1024), (255, 255, 255, 0))
    draw = ImageDraw.Draw(canvas)

    despan_draw(
        draw,
        font_file,
        '<span class="font-bold" style="color: #0000FF">これが青い</span>、またhogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge<span class="font-bold" style="color: #FEDCBA">別の色</span>、<span class="font-bold" style="color: #777777">これがグレー</span>、<span class="font-bold" style="color: #FFFF00">これが黄色い</span>、',
        20,
        (50, 100),
    )
    canvas.save("test.png")
    print("保存:test.png")
```