Working with Images¶
The Glyph SDK provides comprehensive support for working with images in documents, including extraction from DOCX files, inline image rendering in markup, and round-trip workflows.
Overview¶
The image support feature enables:
Image extraction: Extract images from DOCX with full metadata (dimensions, position, alt text)
Inline rendering: Insert images in Glyph markup with precise control over size and alignment
Round-trip workflows: DOCX → Schema → Plaintext → DOCX with images preserved
Workspace management: Automatic image organization and path tracking
Quick Start¶
Rendering Images in Markup¶
from glyph.core.markup.engine.integration import render_markup_to_docx, ImageRegistry
# Create image registry
registry = ImageRegistry()
registry.register("logo", "/path/to/logo.png")
registry.register("chart", "/path/to/chart.jpg")
# Create markup with images
markup = """
$glyph-bold-font-size-18-align-center
Annual Report
$glyph
$glyph-image-id-logo-image-width-2in-image-align-center
$glyph
$glyph-image-id-chart-image-width-5in-image-height-3in
Figure 1: Revenue Growth
$glyph
"""
# Render to DOCX
render_markup_to_docx(markup, output_path="report.docx", image_registry=registry)
Extracting Images from DOCX¶
from glyph.core.schema import GlyphSchemaBuilder
from glyph.core.workspace.storage.fs import FilesystemWorkspace
# Create workspace
workspace = FilesystemWorkspace(use_uuid=True)
# Build schema with image extraction
builder = GlyphSchemaBuilder(
document_xml_path="extracted/word/document.xml",
docx_extract_dir="extracted",
tag="my_project"
)
schema = builder.run(workspace=workspace, copy_images=True)
# Access extracted images
for img in schema["images"]:
print(f"Image: {img['id']}")
print(f" Size: {img['width_inches']} x {img['height_inches']} inches")
print(f" Path: {img['workspace_path']}")
Image Registry¶
The ImageRegistry class maps image IDs to file paths for rendering.
Creating a Registry¶
from glyph.core.markup.engine.integration import ImageRegistry
# Create empty registry
registry = ImageRegistry()
# Create with pre-populated images
registry = ImageRegistry(images={
"logo": "/path/to/logo.png",
"banner": "/path/to/banner.jpg",
})
Registering Images¶
# Register individual images
registry.register("profile", "/path/to/profile.png")
registry.register("signature", "/path/to/signature.jpg")
# Get image path
path = registry.get_path("profile") # Returns "/path/to/profile.png"
# Check if image exists
if "logo" in registry.images:
print("Logo is registered")
# Access via dict-like interface
path = registry["profile"] # Same as get_path()
Bulk Registration¶
Register multiple images from a directory:
from pathlib import Path
registry = ImageRegistry()
image_dir = Path("images/")
for img_file in image_dir.glob("*.png"):
# Use filename (without extension) as ID
img_id = img_file.stem
registry.register(img_id, str(img_file))
From Schema Images¶
When extracting images from a schema:
from glyph.runner import schema_to_plaintext
# Build schema (images extracted automatically)
schema = builder.run(workspace=workspace, copy_images=True)
# Create registry from schema images
registry = ImageRegistry()
for img in schema["images"]:
registry.register(img["id"], img["workspace_path"])
# Generate plaintext
plaintext = schema_to_plaintext(schema)
# Render with images
render_markup_to_docx(plaintext, output_path="output.docx", image_registry=registry)
Image Markup Syntax¶
Basic Image Block¶
$glyph-image-id-{key}
$glyph
Example:
$glyph-image-id-logo
$glyph
With Dimensions¶
Specify width and/or height in inches:
$glyph-image-id-{key}-image-width-{N}in-image-height-{N}in
$glyph
Example:
$glyph-image-id-chart-image-width-5in-image-height-3in
$glyph
$glyph-image-id-banner-image-width-6in
$glyph
Note: If only width or height is specified, the aspect ratio is preserved.
With Alignment¶
Control image alignment:
$glyph-image-id-{key}-image-align-{position}
$glyph
Positions:
left- Left-aligned (default)center- Center-alignedright- Right-aligned
Example:
$glyph-image-id-logo-image-width-2in-image-align-center
$glyph
With Caption¶
Add a caption below the image:
$glyph-image-id-{key}-image-width-{N}in-image-caption-below
Caption text here
$glyph
Example:
$glyph-image-id-chart-image-width-5in-image-caption-below
Figure 1: Quarterly Revenue Growth
$glyph
With Alt Text¶
Provide alternative text for accessibility:
$glyph-image-id-{key}-image-alt-{text}
$glyph
Example:
$glyph-image-id-logo-image-alt-Company Logo
$glyph
Direct Path¶
Reference an image by direct file path (instead of using registry):
$glyph-image-path-{filepath}-image-width-2in
$glyph
Example:
$glyph-image-path-/tmp/generated_chart.png-image-width-4in
$glyph
Note: This requires the file to exist at render time.
Complete Example¶
$glyph-image-id-sales_chart-image-width-5in-image-height-3.5in-image-align-center-image-caption-below
Figure 1: Q4 Sales Performance by Region
$glyph
Image Extraction¶
The schema builder extracts images from DOCX with comprehensive metadata.
Extraction Process¶
from glyph.core.schema import GlyphSchemaBuilder
builder = GlyphSchemaBuilder(
document_xml_path="extracted/word/document.xml",
docx_extract_dir="extracted", # Required for image extraction
)
schema = builder.run()
# Access extracted images
images = schema["images"]
Image Metadata¶
Each extracted image includes:
id: Unique identifier (e.g., “img_1”, “img_2”)name: Image name from DOCXpath: Full path to extracted image filerel_path: Relative path within DOCX (e.g., “media/image1.png”)rId: Relationship ID from document.xml.relswidth_inches: Width in inches (converted from EMUs)height_inches: Height in inches (converted from EMUs)width_emu: Width in EMUs (English Metric Units)height_emu: Height in EMUsparagraph_index: Position in document (which paragraph the image appears in)alt_text: Alternative text (if specified in DOCX)
Example:
{
"id": "img_1",
"name": "Chart",
"path": "/path/to/extracted/word/media/image1.png",
"rel_path": "media/image1.png",
"rId": "rId5",
"width_inches": 5.0,
"height_inches": 3.5,
"width_emu": 4572000,
"height_emu": 3200400,
"paragraph_index": 3,
"alt_text": "Quarterly sales chart"
}
Copying to Workspace¶
Automatically copy extracted images to workspace:
from glyph.core.workspace.storage.fs import FilesystemWorkspace
workspace = FilesystemWorkspace(use_uuid=True)
schema = builder.run(
workspace=workspace,
copy_images=True # Copies images to workspace/input/images/
)
# Images now include workspace_path
for img in schema["images"]:
print(f"Original: {img['path']}")
print(f"Workspace: {img['workspace_path']}")
Workspace Management¶
The workspace provides organized storage for images.
Directory Structure¶
workspace/
├── default/ (or UUID if use_uuid=True)
│ ├── input/
│ │ ├── docx/
│ │ ├── images/ # Extracted images
│ │ │ ├── img001_img_1.png
│ │ │ └── img002_img_2.jpg
│ │ ├── plaintext/
│ │ └── unzipped/
│ └── output/
│ ├── configs/
│ ├── docx/
│ └── plaintext/ # Generated plaintext
Copying Images¶
workspace = FilesystemWorkspace(root_dir="./workspace")
# Copy single image
copied_path = workspace.copy_image(
src_path="/path/to/source.png",
image_id="logo",
index=1 # Optional: creates img001_logo.png
)
# Without index: creates logo.png
copied_path = workspace.copy_image(
src_path="/path/to/source.png",
image_id="logo"
)
Accessing Image Directory¶
# Get image directory path
image_dir = workspace.directory("input_images")
# List all images
from pathlib import Path
images = list(Path(image_dir).glob("*.png"))
Round-Trip Workflows¶
Complete workflow: extract images from DOCX, generate plaintext, then render back.
Full Example¶
from glyph.core.schema import GlyphSchemaBuilder
from glyph.core.workspace.storage.fs import FilesystemWorkspace
from glyph.runner import schema_to_plaintext
from glyph.core.markup.engine.integration import render_markup_to_docx, ImageRegistry
# Step 1: Setup workspace
workspace = FilesystemWorkspace(root_dir="./workspace", use_uuid=True)
# Step 2: Extract DOCX (unzip first)
# unzip source.docx -d extracted/
# Step 3: Build schema with image extraction
builder = GlyphSchemaBuilder(
document_xml_path="extracted/word/document.xml",
docx_extract_dir="extracted",
tag="project_v1"
)
schema = builder.run(workspace=workspace, copy_images=True)
print(f"Extracted {len(schema['images'])} images")
# Step 4: Generate plaintext with image references
plaintext_path = schema_to_plaintext(
schema,
output_path=workspace.directory("output_plaintext") + "/output.glyph.txt"
)
print(f"Generated plaintext: {plaintext_path}")
# Step 5: Create image registry from extracted images
registry = ImageRegistry()
for img in schema["images"]:
registry.register(img["id"], img["workspace_path"])
# Step 6: Render plaintext back to DOCX
with open(plaintext_path) as f:
markup = f.read()
output_docx = render_markup_to_docx(
markup,
output_path=workspace.directory("output_docx") + "/reconstructed.docx",
image_registry=registry
)
print(f"Generated DOCX: {output_docx}")
Editing Workflow¶
Extract, edit plaintext, then render:
# 1. Extract to plaintext (as above)
schema = builder.run(workspace=workspace, copy_images=True)
plaintext = schema_to_plaintext(schema, output_path="editable.glyph.txt")
# 2. User edits editable.glyph.txt
# - Modify text content
# - Adjust image sizes
# - Change image alignment
# - Add/remove images
# 3. Render edited markup
registry = ImageRegistry()
for img in schema["images"]:
registry.register(img["id"], img["workspace_path"])
with open("editable.glyph.txt") as f:
edited_markup = f.read()
render_markup_to_docx(
edited_markup,
output_path="final.docx",
image_registry=registry
)
Advanced Usage¶
Custom Image Processing¶
Process images before rendering:
from PIL import Image
import os
def resize_image(src_path, max_width=800):
"""Resize image to max width while preserving aspect ratio."""
img = Image.open(src_path)
if img.width > max_width:
ratio = max_width / img.width
new_height = int(img.height * ratio)
img = img.resize((max_width, new_height), Image.LANCZOS)
# Save resized
resized_path = src_path.replace(".png", "_resized.png")
img.save(resized_path)
return resized_path
return src_path
# Use in workflow
registry = ImageRegistry()
for img in schema["images"]:
resized_path = resize_image(img["workspace_path"])
registry.register(img["id"], resized_path)
Dynamic Image Generation¶
Generate images programmatically:
import matplotlib.pyplot as plt
def generate_chart(data, output_path):
"""Generate a chart from data."""
plt.figure(figsize=(5, 3))
plt.bar(data.keys(), data.values())
plt.title("Sales by Region")
plt.savefig(output_path, dpi=300, bbox_inches='tight')
plt.close()
# Generate and register
chart_path = "/tmp/sales_chart.png"
generate_chart({"North": 100, "South": 150, "East": 120, "West": 180}, chart_path)
registry = ImageRegistry()
registry.register("sales_chart", chart_path)
# Use in markup
markup = """
$glyph-image-id-sales_chart-image-width-5in-image-align-center
Figure 1: Regional Sales
$glyph
"""
Image Format Support¶
Supported formats:
PNG (.png)
JPEG (.jpg, .jpeg)
GIF (.gif)
BMP (.bmp)
TIFF (.tiff, .tif)
Note: python-docx requires the image file to be accessible at render time.
Troubleshooting¶
Common Issues¶
“Image not found in registry”
Ensure the image ID matches exactly:
# Markup uses: image-id-logo
# Registry must have:
registry.register("logo", "/path/to/logo.png") # ✓ Correct
“FileNotFoundError: image file not found”
Verify the image path exists:
import os
path = "/path/to/image.png"
if not os.path.exists(path):
print(f"Image not found: {path}")
Images appear too large/small
Specify explicit dimensions:
$glyph-image-id-logo-image-width-2in-image-height-1.5in
$glyph
Images not extracted from DOCX
Ensure extract directory is provided:
builder = GlyphSchemaBuilder(
document_xml_path="document.xml",
docx_extract_dir="extracted/" # Required!
)
Best Practices¶
Use consistent image IDs: Prefer descriptive names (
logo,chart_q4) over generic ones (img1,img2)Specify dimensions: Always provide width/height for consistent rendering
Organize images: Use workspace for automatic organization
Optimize image sizes: Resize images before adding to documents to reduce file size
Provide alt text: Include
image-altfor accessibilityUse captions: Add
image-caption-belowfor figures and chartsVersion control: Store image registry configuration in code:
# config.py IMAGE_PATHS = { "logo": "assets/logo.png", "banner": "assets/banner.jpg", } # main.py registry = ImageRegistry(images=IMAGE_PATHS)
Performance Tips¶
Batch processing: Register all images before rendering
Cache resized images: Avoid re-processing images on each render
Use appropriate formats: PNG for logos/screenshots, JPEG for photos
Optimize images: Compress images before adding to registry