PicTex

repository·main·Indexed 42 days ago

https://github.com/francozanardi/pictex

A Python library for programmatically generating high-quality images using CSS Flexbox layouts. It utilizes Skia for rendering and Taffy for layout, allowing developers to build complex visuals like social media cards or data tables using web standards. Version 2.3.0 supports a fluent API for styling components such as Row, Column, Image, and Text, and provides both BitmapImage and VectorImage outputs.

Tokens
19.1K
Snippets
68
Records
77
Agent score
52%

What's inside PicTex

  1. Overview of PicTex

    main
    PicTex is a Python library designed for creating styled visual compositions. It provides a component-based and chainable interface that abstracts low-level graphics engines, making it suitable for generating dynamic social media images, video overlays, or digital art tools. Key capabilities include a powerful layout engine for composing elements, a flexible box model for styling, and support for exporting to both raster and vector formats.
  2. How style inheritance works in Canvas

    main

    The Canvas acts as a global style template. Properties set on the Canvas are inherited by all child builders unless they are explicitly overridden on the individual builder.

    Inherited properties include:

    • Typography: font_family, font_size, font_weight, font_style, color
    • Text layout: text_align, line_height
    • Text effects: text_shadows, text_stroke, underline, strikethrough

    Properties that are NOT inherited (element-specific):

    • Box model: padding, margin, border, border_radius
    • Background: background_color, background_image
    • Effects: box_shadows
    • Layout: size, position, flex_grow, etc.
    from pictex import Canvas, Column, Text
    
    # Canvas sets global typography
    canvas = (
        Canvas()
        .font_family("Arial")
        .font_size(75)
        .color("blue")
    )
    
    # These inherit the font settings but can override them
    layout = Column(
        Text("Inherits blue"),
        Text("Overrides to red").color("red"),
        Text("Inherits blue again")
    )
    
    canvas.render(layout).save("inheritance_example.png")
  3. Understand the Builder concept in PicTex

    main

    In PicTex, every visual element is a Builder. You compose images by nesting different types of builders together. There are three main categories:

    1. Content Builders: The basic building blocks for content.
      • Text(): For displaying text.
      • Image(): For displaying raster images.
    2. Layout Builders: Containers that arrange other builders using flexbox principles.
      • Row(): Arranges children horizontally.
      • Column(): Arranges children vertically.
    3. Root Builder:
      • Canvas(): The top-level container that holds the entire composition and defines global styles. It is used to trigger the .render() process.

    You can nest these builders (e.g., putting Text builders inside a Column builder, which is itself inside a Row builder) to create complex visual layouts.

    from pictex import *
    
    composition = Row(
        Column(
            Text("Col1").background_color("blue"),
            Text("Col1").background_color("red")
        ),
        Column(
            Text("Col2").background_color("blue")
            Text("Col2").background_color("red")
        )
    )
    Canvas().render(composition).save("introduction.png")
  4. Inspect the rendered element hierarchy using Render Tree classes

    main

    To access or manipulate the hierarchical structure of rendered elements, use the Render Tree classes:

    • pictex.RenderNode: Represents an individual node within the rendered tree structure.
    • pictex.NodeType: An enumeration used to identify the type of a specific RenderNode.
  5. How Font Fallbacks and Emoji Support work

    main

    PicTex uses an automatic fallback system to prevent missing characters (rendered as □). If a character (like an emoji or a specific language script) is not supported by your primary font, PicTex follows this chain:

    1. Primary Font: Set via .font_family() (defaults to bundled Inter Variable).
    2. Custom Fallbacks: Any fonts provided via .font_fallbacks().
    3. System Discovery: Automatic search through system fonts for a compatible grapheme.

    Use .font_fallbacks() to provide a specific list of fonts for multi-lingual support.

    from pictex import Canvas
    
    primary_font = "Lato-BoldItalic.ttf" 
    japanese_font = "NotoSansJP-Regular.ttf"
    
    canvas = (
        Canvas()
        .font_family(primary_font)
        .font_fallbacks(japanese_font)
        .font_size(80)
        .color("olive")
        .padding(20)
    )
    
    text = "Hello, 世界 ✨"
    canvas.render(text).save("font_fallback_example.png")
  6. Understand PicTex output types: BitmapImage and VectorImage

    main

    PicTex compositions result in one of two primary output classes depending on the rendering method used:

    • pictex.BitmapImage: Represents a rasterized image output (e.g., PNG, JPEG).
    • pictex.VectorImage: Represents a vector-based image output (e.g., SVG), which allows for infinite scaling without loss of quality.

    When working with the PicTex API, the specific class returned will determine how you save or display the final rendered product.

  7. Configure element sizing with .size()

    main

    The .size() method sets explicit dimensions for an element. PicTex uses a border-box model, meaning the size you define is the total visible dimension, including padding and borders. Padding and borders are applied inward, reducing the available space for content.

    Sizing Modes

    Value TypeExampleBehavior
    'auto' (Default)size()Context-dependent. Usually wraps content, but allows the element to be stretched by parent alignment properties.
    'fit-content'size(height='fit-content')Forces the element to wrap its content exactly, preventing parent stretching.
    'fit-background-image'size(width='fit-background-image')Forces the element to match the dimensions of its background image.
    Absolute (pixels)size(width=200, height=150)Sets a fixed pixel size, overriding parent stretching.
    Percentagesize(width="50%")Sets size as a percentage of the parent's content area, overriding parent stretching.

    Note: To fill available space in a layout, use .flex_grow(1) instead of sizing modes.

    # This element's final width in the layout is exactly 300px.
    box = Row(Text("Content"))\\
        .size(width=300)\\
        .padding(20)\\
        .border(5, "blue")
    
    # The content area inside is now 300 - (2*20 padding) - (2*5 border) = 250px wide.
  8. Use different positioning modes: absolute, fixed, and relative

    main

    PicTex supports three CSS-compliant positioning modes to control how elements relate to their containers or the canvas:

    1. absolute_position(): Positions the element relative to its nearest ancestor (similar to CSS position: absolute).
    2. fixed_position(): Positions the element relative to the canvas viewport, regardless of nesting or parent position (similar to CSS position: fixed).
    3. relative_position(): Positions the element relative to its normal flow position (where it would naturally sit in the flex layout).

    When using these modes, the element is removed from the normal layout flow.

    from pictex import Canvas, Text
    
    (
        Canvas()
        .margin(25)
        .padding(25)
        .border(25, "red")
        .background_color("blue")
        .size(200, 200)
        .font_size(20)
        .color("orange")
        .render(
            Text("FIXED").fixed_position(top=0, left=0),
            Text("ABSOLUTE").absolute_position(top=0, left=0)
        )
    ).save("position.png")
  9. Handle Text Direction (LTR / RTL) and BiDi

    main

    PicTex automatically applies the Unicode Bidirectional Algorithm (BiDi) to handle mixed-direction content (e.g., English and Arabic) without manual configuration.

    Key behaviors:

    • Automatic Alignment: In an RTL context, text_align() defaults to TextAlign.RIGHT.
    • Layout Mirroring: Row containers automatically reverse the visual order of their children when .direction("rtl") is applied.
    • Direction Inheritance: The direction property is inherited by all children.
    • Manual Override: Use .direction("rtl") or .direction("ltr") to set an explicit base direction.
    from pictex import Canvas, Row, Column, Text
    
    # RTL base direction - affects alignment and layout
    container = (
        Column(
            # This Row will be mirrored: [B] [A]
            Row(
                Text("A").background_color("red").padding(5),
                Text("B").background_color("orange").padding(5),
            ).gap(10),
    
            # This text will be right-aligned automatically
            Text("Right-aligned text").size(width=400), 
            
            # Explicit override to LTR
            Text("LTR Override").direction("ltr"), 
        )
        .direction("rtl")
        .padding(20)
        .gap(20)
        .background_color("lightgray")
    )
    
    Canvas().render(container).save("direction_example.png")
  10. Configure font handling for SVG exports

    main

    When exporting to SVG, font portability is managed via the embed_font parameter in .render_as_svg(). The behavior depends on whether you are using a font file path or a system font name.

    Using a Font File (e.g., .font_family("path/to/font.ttf"))

    • embed_font=True (Default): The font is Base64 encoded and embedded directly in the SVG. This creates a fully self-contained and portable file, but increases the file size by approximately 133% of the original font size.
    • embed_font=False: The SVG references the font, and PicTex automatically copies the font files to a subdirectory. This creates a portable package where the SVG is small, but must be distributed alongside its fonts/ directory.

    Customizing Font Copying:

    • Use fonts_subdir="name" to change the destination directory name.
    • Use copy_fonts=False to disable automatic copying entirely.

    Using a System Font (e.g., .font_family("Arial"))

    • The embed_font parameter has no effect because PicTex cannot access the file path of a system font.
    • The SVG will reference the font by name (e.g., font-family: 'Arial').
    • Rendering depends on the viewer having the font installed. If the font is missing, the viewer will use a fallback, which may change the appearance.
    • A warning is issued if you attempt to use embed_font=True with a system font.
    # This creates a completely portable SVG (Base64 embedded)
    vector_image = canvas.render_as_svg("Portable & Perfect", embed_font=True)
    vector_image.save("portable_text.svg")
    
    # This creates a lightweight SVG with automatic font copying to a custom directory
    vector_image = canvas.render_as_svg("Linked Font", embed_font=False)
    vector_image.save("output/linked_text.svg", fonts_subdir="my-fonts")
    
    # This disables automatic font copying (SVG will only work if fonts exist in the same directory)
    vector_image.save("output/linked_text.svg", copy_fonts=False)
  11. How builders and Canvas work together

    main

    PicTex uses a compositional model to create visuals. The workflow consists of three main parts:

    1. Builders: The fundamental building blocks.
      • Content Builders: Such as Text and Image.
      • Layout Builders: Such as Row and Column used to arrange content builders.
    2. Canvas: The top-level container. It is used to set global styles (like default fonts or background colors) and serves as the entry point for rendering.
    3. Rendering: You create a tree structure by nesting builders inside one another, then pass that tree to a Canvas instance to produce a final image.

    This tree structure allows you to build complex components (like a user banner) by nesting simple elements (like an avatar and text) inside layout containers.

    from pictex import Canvas, Row, Column, Text, Image
    
    # Compose a tree of builders
    avatar = Image("avatar.jpg").size(60, 60).border_radius('50%')
    user_info = Column(Text("Alex Doe"), Text("@alexdoe"))
    user_banner = Row(avatar, user_info)
    
    # Render via Canvas
    canvas = Canvas()
    image = canvas.render(user_banner)
  12. Quickstart: Create a composed image

    main

    To create a composed image, define your content and layout builders, nest them, and then use a Canvas to render the result. You can then save the resulting BitmapImage to a file.

    from pictex import Canvas, Row, Column, Text, Image
    
    # 1. Create the individual content builders
    avatar = (
        Image("avatar.jpg")
        .size(60, 60)
        .border_radius('50%') # Make it circular
    )
    
    user_info = Column(
        Text("Alex Doe").font_size(20).font_weight(700),
        Text("@alexdoe").color("#657786")
    ).gap(4) # Add a 4px vertical gap between the texts
    
    # 2. Compose the builders in a layout container
    user_banner = Row(
        avatar,
        user_info
    ).gap(15).align_items('center')  # Vertically center the avatar and user info
    
    # 3. Create a Canvas and render the final composition
    canvas = Canvas().padding(20).background_color("#F5F8FA")
    image = canvas.render(user_banner)
    
    # 4. Save the result
    image.save("user_banner.png")