Cover rendering API (bubble.cover_*)¶
Peanutbook ships reusable Python helpers for print full-wrap covers (back + spine + front). They live in the bubble package under shared/bubble/ and are used by project scripts such as cover/7x10/cover_back_v2_draw.py, gen_fullcover.py, and full_cover_from_front_back_en.py.
Install peanutbook (or add shared/ to PYTHONPATH) and import from bubble:
from bubble.cover_fonts import get_cover_font_path, load_font, wrap_words
from bubble.cover_draw import draw_bullet_dot_pil, wrap_bullet_list
from bubble.cover_print import get_cover_spec, spread_pixels
Module overview¶
| Module | Purpose |
|---|---|
bubble.cover_draw |
List bullets (matplotlib + PIL), semi-opaque rounded boxes, inch→pixel helpers |
bubble.cover_fonts |
Western serif font stack, PIL load_font, word wrap |
bubble.cover_export |
save_small_jpeg — KDP/web preview thumbnails |
bubble.cover_print |
Vendor print specs (CoverPrintSpec, spread_pixels) + bundled JSON |
bubble.cover_image |
Lower-band dimming (dim_image_lower) for background PNGs |
bubble.cover_spread |
Panel paste into bleed slots, spine photos, background recompose |
bubble.cover_spine |
Spine bridge/fog masks, rotated spine typography |
Book-specific layout (copy, colors, positions) stays in cover/{size}/ scripts. Shared geometry and drawing primitives live in bubble.
bubble.cover_print¶
Loads dimensions from bubble/data/cover_print_config.json (shipped with the package). Projects can override by passing config_path or keeping a local cover_print_config.json (as in cover/7x10/).
Key types¶
CoverPrintSpec— full spread size in inches + dpi (provider, bleed, panel trim, spine width).SpreadPixels— pixel layout derived from a spec (back_x0,spine_x0,front_x0,panel_w,spine_px, content centers).
Functions¶
from pathlib import Path
from bubble.cover_print import (
get_cover_spec,
get_default_provider,
list_providers,
normalize_provider,
provider_slug,
spread_pixels,
guess_cover_spec_from_size,
default_config_path,
)
spec = get_cover_spec("en", provider="kdp/paperback")
px = spread_pixels(spec)
# px.total_w, px.back_x0, px.front_content_cx, ...
# Optional: project-local JSON
local = Path("cover/7x10/cover_print_config.json")
spec = get_cover_spec("en", provider="ingram/hardcover", config_path=local)
Provider IDs use vendor/binding form, e.g. kdp/paperback, ingram/hardcover. Legacy aliases kdp_paperback, ingram_hardcover are accepted.
bubble-batch and gen_fullcover.py use the same provider strings via --cover-provider.
bubble.cover_fonts¶
Resolves a Western serif font file (EB Garamond preferred) and loads it for PIL at a given point size and dpi.
from bubble.cover_fonts import (
SERIF_EN_DEFAULT,
get_cover_font_path,
load_font,
wrap_words,
pt2px,
text_bbox,
)
from PIL import ImageDraw, Image
font_path = get_cover_font_path()
body = load_font(font_path, 12.8, dpi=300)
draw = ImageDraw.Draw(Image.new("RGB", (800, 200)))
lines = wrap_words(draw, "Long paragraph …", body, max_width_px=600)
bubble.cover_draw¶
Bullets¶
EB Garamond’s Unicode bullet (U+2022) renders as a hollow square in PIL. Use drawn circular bullets instead:
from bubble.cover_draw import (
BULLET_CHAR,
BULLET_SCALE_DEFAULT,
BULLET_STROKE_PT,
bullet_text_indent_px,
draw_bullet_dot_pil,
wrap_bullet_list,
draw_wrapped_bullets_pil,
)
content = "• First item\n• Second item"
indent = bullet_text_indent_px(12.8, dpi=300)
rows = wrap_bullet_list(draw, content, font, max_w, indent, wrap_words)
for need_bullet, line in rows:
if need_bullet:
x = draw_bullet_dot_pil(draw, x0, y, 12.8, (255, 255, 255), 300)
...
Or one-shot: draw_wrapped_bullets_pil(...).
Matplotlib back covers can use body_text_with_bullets and draw_bullet_dot on an axes object.
Layout primitives¶
from bubble.cover_draw import draw_semiopaque_rounded_rect, y_from_bottom_in_to_px
y = y_from_bottom_in_to_px(3.25, y0, panel_h_px, dpi=300) # inches from bottom
draw_semiopaque_rounded_rect(img, x, y, w, h, radius_px, alpha=0.75)
bubble.cover_export¶
from bubble.cover_export import save_small_jpeg, SMALL_JPEG_LINEAR_SCALE
preview = save_small_jpeg(full_cover_img, Path("out/cover.jpg"), dpi=300)
# → out/cover_small.jpg at ~0.33× linear scale
bubble.cover_image¶
Soft darkening of the lower band of a cover background (numpy array or file pipeline):
from bubble.cover_image import dim_image_lower, write_lower_band_bkg
arr = dim_image_lower(arr, frac_lower=0.2, brightness_factor=0.13)
write_lower_band_bkg("cover_front_bkg.png", "cover_front_bkg_v2.png", input_dir=Path("input"))
CLI (from a book repo): python cover/7x10/image_lower_band.py still works; it delegates to bubble.cover_image.
bubble.cover_spread¶
Full-wrap assembly helpers:
from bubble.cover_spread import (
paste_panels_into_bleed_slots,
synthesize_bleed_from_inner_edge,
fit_spine_top_photo,
paste_spine_photo_top,
load_bkg_for_spec,
recompose_bkg_for_target,
pil_resample_lanczos,
is_hardcover_provider,
)
rs = pil_resample_lanczos()
paste_panels_into_bleed_slots(canvas, back_panel, front_panel, spread_pixels(spec), rs)
# Resize a KDP spread PNG to Ingram hardcover geometry
img = load_bkg_for_spec(Path("out/full_cover_from_bkg_en.png"), target_spec, "en", resample=rs)
bubble.cover_spine¶
Spine bridge compositing between back and front panels, plus rotated title/subtitle/author text (default 270° CCW):
from bubble.cover_spine import (
make_bridge_base,
flat_top_spine_mask,
edge_zero_fog_mask,
draw_spine_title_lines,
draw_rotated_spine_text_top,
spine_safe_band,
measure_rotated_text_block,
)
bridge = make_bridge_base(back, front, edge_fog_w, spine_px, panel_w, panel_h, resample)
top, bottom, title_y = spine_safe_band(by0, panel_h, center_inset_frac=0.08, photo_zone_h_px=260)
y = draw_spine_title_lines(canvas, title_lines, spine_cx, title_y, title_font, rotate_deg=270)
Typical project layout¶
cover/7x10/
constant_text.py # book copy (titles, features, spine strings)
cover_print_config.json # optional override of bundled print sizes
cover_back_v2_draw.py # back panel layout (imports bubble.*)
cover_front_v2_draw.py # front panel + low-poly background
full_cover_from_front_back_en.py # spread background + spine
gen_fullcover.py # overlay text on spread PNG
cover_print_config.py # thin re-export → bubble.cover_print (local JSON default)
cover_export.py # thin re-export → bubble.cover_export
Regenerate English 7×10 back cover:
cd cover/7x10
PYTHONPATH=../../shared:$PYTHONPATH python cover_back_v2_draw.py
Full spread with provider:
python full_cover_from_front_back_en.py --provider ingram/hardcover
python gen_fullcover.py --provider ingram/hardcover
See also Covers & templates for CLI (bubble-batch --cover-provider) and template mapping.