diff --git a/app/app.py b/app/app.py index 7b95179..62fea86 100644 --- a/app/app.py +++ b/app/app.py @@ -107,31 +107,18 @@ def object_box(obj: dict[str, Any], scale: float) -> tuple[int, int, int, int]: return x, y, max(1, w), max(1, h) -def wrap_text(draw: ImageDraw.ImageDraw, text: str, font, max_width: int) -> list[str]: - lines: list[str] = [] - for raw_line in text.splitlines() or [""]: - current = "" - for word in raw_line.split(" "): - candidate = word if not current else f"{current} {word}" - if draw.textbbox((0, 0), candidate, font=font)[2] <= max_width: - current = candidate - continue - if current: - lines.append(current) - current = word - if current: - lines.append(current) - return lines +def text_lines(text: str) -> list[str]: + return text.split("\n") -def draw_text(draw: ImageDraw.ImageDraw, obj: dict[str, Any], scale: float) -> None: +def draw_text(image: Image.Image, draw: ImageDraw.ImageDraw, obj: dict[str, Any], scale: float) -> None: x, y, w, h = object_box(obj, scale) text = str(obj.get("text", "Text")) font_size = scaled_mm_to_px(float(obj.get("fontSizeMm", 5)), scale) font = get_font(font_size, bool(obj.get("bold", False))) align = obj.get("align", "left") valign = obj.get("valign", "middle") - lines = wrap_text(draw, text, font, w) + lines = text_lines(text) boxes = [draw.textbbox((0, 0), line, font=font) for line in lines] line_heights = [box[3] - box[1] for box in boxes] gap = max(1, round(font_size * 0.15)) @@ -150,7 +137,15 @@ def draw_text(draw: ImageDraw.ImageDraw, obj: dict[str, Any], scale: float) -> N cx = x + w - text_w else: cx = x - draw.text((cx, cy - box[1]), line, font=font, fill=1) + text_layer = Image.new("1", image.size, 0) + text_draw = ImageDraw.Draw(text_layer) + text_draw.text((cx, cy - box[1]), line, font=font, fill=1) + clip = Image.new("1", image.size, 0) + clip_draw = ImageDraw.Draw(clip) + clip_draw.rectangle([x, y, x + w, y + h], fill=1) + clipped = Image.new("1", image.size, 0) + clipped.paste(text_layer, mask=clip) + image.paste(1, mask=clipped) cy += line_h + gap @@ -283,7 +278,7 @@ def render_design_image(design: dict[str, Any]) -> Image.Image: continue obj_type = obj.get("type") if obj_type == "text" or obj_type == "symbol": - draw_text(draw, obj, scale) + draw_text(image, draw, obj, scale) elif obj_type == "shape": draw_shape(draw, obj, scale) elif obj_type == "line": diff --git a/app/static/editor.js b/app/static/editor.js index 8484ccf..43ada78 100644 --- a/app/static/editor.js +++ b/app/static/editor.js @@ -102,22 +102,8 @@ function clamp(value, min, max) { return Math.min(max, Math.max(min, value)); } -function wrapCanvasText(text, maxWidthPx) { - const lines = []; - for (const rawLine of String(text || "").split("\n")) { - let current = ""; - for (const word of rawLine.split(" ")) { - const candidate = current ? `${current} ${word}` : word; - if (ctx.measureText(candidate).width <= maxWidthPx) { - current = candidate; - continue; - } - if (current) lines.push(current); - current = word; - } - lines.push(current); - } - return lines.length ? lines : [""]; +function canvasTextLines(text) { + return String(text || "").split("\n"); } function textNaturalWidthMm(obj) { @@ -149,7 +135,7 @@ function objectContentEndMm(obj) { const { value } = objectFont(obj); ctx.save(); ctx.font = value; - const lines = wrapCanvasText(obj.text || "", mmToPx(obj.w)); + const lines = canvasTextLines(obj.text || ""); const textWidthMm = Math.max(...lines.map((line) => pxToMm(ctx.measureText(line).width)), 0); ctx.restore(); @@ -255,7 +241,7 @@ function drawTextObject(obj) { ctx.font = font; ctx.textBaseline = "middle"; ctx.textAlign = obj.align || "left"; - const lines = wrapCanvasText(obj.text || "", w); + const lines = canvasTextLines(obj.text || ""); const lineHeight = fontPx * 1.15; const total = lineHeight * lines.length; let cy = y + h / 2 - total / 2 + lineHeight / 2;