Add text fit safety margin
This commit is contained in:
+40
-1
@@ -43,6 +43,7 @@ PRINT_QUEUE_ENABLED = os.environ.get("PRINT_QUEUE_ENABLED", "1").lower() in {"1"
|
|||||||
PRINT_QUEUE_RETRY_SECONDS = int(os.environ.get("PRINT_QUEUE_RETRY_SECONDS", "30"))
|
PRINT_QUEUE_RETRY_SECONDS = int(os.environ.get("PRINT_QUEUE_RETRY_SECONDS", "30"))
|
||||||
DPI = int(os.environ.get("LABEL_DPI", "180"))
|
DPI = int(os.environ.get("LABEL_DPI", "180"))
|
||||||
PTOUCH_SUPPORTS_COPIES: bool | None = None
|
PTOUCH_SUPPORTS_COPIES: bool | None = None
|
||||||
|
TEXT_FIT_EXTRA_MM = 6
|
||||||
|
|
||||||
TAPE_WIDTHS_MM = [3.5, 6, 9, 12, 18, 24]
|
TAPE_WIDTHS_MM = [3.5, 6, 9, 12, 18, 24]
|
||||||
PRINTABLE_TAPE_PX = {
|
PRINTABLE_TAPE_PX = {
|
||||||
@@ -111,6 +112,43 @@ def text_lines(text: str) -> list[str]:
|
|||||||
return text.split("\n")
|
return text.split("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def text_natural_width_mm(obj: dict[str, Any], scale: float) -> float:
|
||||||
|
font_size = scaled_mm_to_px(float(obj.get("fontSizeMm", 5)), scale)
|
||||||
|
font = get_font(font_size, bool(obj.get("bold", False)))
|
||||||
|
scratch = Image.new("1", (1, 1), 0)
|
||||||
|
scratch_draw = ImageDraw.Draw(scratch)
|
||||||
|
widths = []
|
||||||
|
for line in text_lines(str(obj.get("text", "")) or " "):
|
||||||
|
box = scratch_draw.textbbox((0, 0), line or " ", font=font)
|
||||||
|
widths.append((box[2] - box[0]) / scale)
|
||||||
|
return max(2, max(widths, default=0) + TEXT_FIT_EXTRA_MM)
|
||||||
|
|
||||||
|
|
||||||
|
def autofit_text_objects_for_render(design: dict[str, Any], scale: float) -> dict[str, Any]:
|
||||||
|
if not bool(design.get("autoLength", True)):
|
||||||
|
return design
|
||||||
|
fitted = dict(design)
|
||||||
|
objects = []
|
||||||
|
margin = float(fitted.get("marginMm", 2))
|
||||||
|
for obj in fitted.get("objects", []):
|
||||||
|
if not isinstance(obj, dict):
|
||||||
|
objects.append(obj)
|
||||||
|
continue
|
||||||
|
item = dict(obj)
|
||||||
|
if item.get("type") in {"text", "symbol"} and not item.get("manualSize"):
|
||||||
|
item["w"] = text_natural_width_mm(item, scale)
|
||||||
|
objects.append(item)
|
||||||
|
fitted["objects"] = objects
|
||||||
|
length_end = margin
|
||||||
|
orientation = fitted.get("orientation", "landscape")
|
||||||
|
for obj in objects:
|
||||||
|
if not isinstance(obj, dict) or obj.get("hidden"):
|
||||||
|
continue
|
||||||
|
length_end = max(length_end, float(obj.get("y", 0)) + float(obj.get("h", 0)) if orientation == "portrait" else float(obj.get("x", 0)) + float(obj.get("w", 0)))
|
||||||
|
fitted["lengthMm"] = max(float(fitted.get("lengthMm", 70)), min(500, length_end + margin))
|
||||||
|
return fitted
|
||||||
|
|
||||||
|
|
||||||
def draw_text(image: Image.Image, 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)
|
x, y, w, h = object_box(obj, scale)
|
||||||
text = str(obj.get("text", "Text"))
|
text = str(obj.get("text", "Text"))
|
||||||
@@ -261,6 +299,8 @@ def render_design_image(design: dict[str, Any]) -> Image.Image:
|
|||||||
tape_width = float(design.get("tapeWidthMm", 24))
|
tape_width = float(design.get("tapeWidthMm", 24))
|
||||||
if tape_width not in TAPE_WIDTHS_MM:
|
if tape_width not in TAPE_WIDTHS_MM:
|
||||||
raise ValueError("Ungueltige Bandbreite.")
|
raise ValueError("Ungueltige Bandbreite.")
|
||||||
|
scale = render_scale_for_tape(tape_width)
|
||||||
|
design = autofit_text_objects_for_render(design, scale)
|
||||||
length = clamp(float(design.get("lengthMm", 70)), 10, 500)
|
length = clamp(float(design.get("lengthMm", 70)), 10, 500)
|
||||||
orientation = design.get("orientation", "landscape")
|
orientation = design.get("orientation", "landscape")
|
||||||
if orientation == "portrait":
|
if orientation == "portrait":
|
||||||
@@ -268,7 +308,6 @@ def render_design_image(design: dict[str, Any]) -> Image.Image:
|
|||||||
else:
|
else:
|
||||||
canvas_w_mm, canvas_h_mm = length, tape_width
|
canvas_w_mm, canvas_h_mm = length, tape_width
|
||||||
|
|
||||||
scale = render_scale_for_tape(tape_width)
|
|
||||||
image = new_label_image(scaled_mm_to_px(canvas_w_mm, scale), scaled_mm_to_px(canvas_h_mm, scale))
|
image = new_label_image(scaled_mm_to_px(canvas_w_mm, scale), scaled_mm_to_px(canvas_h_mm, scale))
|
||||||
max_height_px = printable_px_for_tape(tape_width)
|
max_height_px = printable_px_for_tape(tape_width)
|
||||||
if image.height > max_height_px:
|
if image.height > max_height_px:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const PRINTABLE_TAPE_PX = {
|
|||||||
24: 128,
|
24: 128,
|
||||||
};
|
};
|
||||||
const STORAGE_KEY = "ptouch-web-layout";
|
const STORAGE_KEY = "ptouch-web-layout";
|
||||||
|
const TEXT_FIT_EXTRA_MM = 6;
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
zoom: 1.5,
|
zoom: 1.5,
|
||||||
@@ -113,7 +114,7 @@ function textNaturalWidthMm(obj) {
|
|||||||
const lines = String(obj.text || " ").split("\n");
|
const lines = String(obj.text || " ").split("\n");
|
||||||
const maxTextWidthPx = Math.max(...lines.map((line) => ctx.measureText(line || " ").width), 0);
|
const maxTextWidthPx = Math.max(...lines.map((line) => ctx.measureText(line || " ").width), 0);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
return Math.max(2, pxToMm(maxTextWidthPx) + 1);
|
return Math.max(2, pxToMm(maxTextWidthPx) + TEXT_FIT_EXTRA_MM);
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoFitTextObject(obj) {
|
function autoFitTextObject(obj) {
|
||||||
|
|||||||
Reference in New Issue
Block a user