added fotn change API and examples
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
"""
|
||||
Demonstration of dynamic font family switching in the ereader.
|
||||
|
||||
This example shows how to:
|
||||
1. Initialize an ereader with content
|
||||
2. Dynamically switch between different font families (Sans, Serif, Monospace)
|
||||
3. Maintain reading position across font changes
|
||||
4. Use the font family API
|
||||
|
||||
The ereader manager provides a high-level API for changing fonts on-the-fly
|
||||
without losing your place in the document.
|
||||
"""
|
||||
|
||||
from pyWebLayout.abstract import Paragraph, Heading, Word
|
||||
from pyWebLayout.abstract.block import HeadingLevel
|
||||
from pyWebLayout.style import Font
|
||||
from pyWebLayout.style.fonts import BundledFont, FontWeight
|
||||
from pyWebLayout.style.page_style import PageStyle
|
||||
from pyWebLayout.layout.ereader_manager import create_ereader_manager
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def create_sample_content():
|
||||
"""Create sample document content with various text styles"""
|
||||
blocks = []
|
||||
|
||||
# Create a default font for the content
|
||||
default_font = Font.from_family(BundledFont.SANS, font_size=16)
|
||||
heading_font = Font.from_family(BundledFont.SANS, font_size=24, weight=FontWeight.BOLD)
|
||||
|
||||
# Title
|
||||
title = Heading(level=HeadingLevel.H1, style=heading_font)
|
||||
for word in "Font Family Switching Demo".split():
|
||||
title.add_word(Word(word, heading_font))
|
||||
blocks.append(title)
|
||||
|
||||
# Introduction paragraph
|
||||
intro_font = Font.from_family(BundledFont.SANS, font_size=16)
|
||||
intro = Paragraph(intro_font)
|
||||
intro_text = (
|
||||
"This demonstration shows how the ereader can dynamically switch between "
|
||||
"different font families while maintaining your reading position. "
|
||||
"The three bundled font families (Sans, Serif, and Monospace) can be "
|
||||
"changed on-the-fly without recreating the document."
|
||||
)
|
||||
for word in intro_text.split():
|
||||
intro.add_word(Word(word, intro_font))
|
||||
blocks.append(intro)
|
||||
|
||||
# Section 1
|
||||
section1_heading = Heading(level=HeadingLevel.H2, style=heading_font)
|
||||
for word in "Sans-Serif Font".split():
|
||||
section1_heading.add_word(Word(word, heading_font))
|
||||
blocks.append(section1_heading)
|
||||
|
||||
para1 = Paragraph(default_font)
|
||||
text1 = (
|
||||
"Sans-serif fonts like DejaVu Sans are clean and modern, making them "
|
||||
"ideal for screen reading. They lack the decorative strokes (serifs) "
|
||||
"found in traditional typefaces, which can improve legibility on digital displays. "
|
||||
"Many ereader applications default to sans-serif fonts for this reason."
|
||||
)
|
||||
for word in text1.split():
|
||||
para1.add_word(Word(word, default_font))
|
||||
blocks.append(para1)
|
||||
|
||||
# Section 2
|
||||
section2_heading = Heading(level=HeadingLevel.H2, style=heading_font)
|
||||
for word in "Serif Font".split():
|
||||
section2_heading.add_word(Word(word, heading_font))
|
||||
blocks.append(section2_heading)
|
||||
|
||||
para2 = Paragraph(default_font)
|
||||
text2 = (
|
||||
"Serif fonts like DejaVu Serif have small decorative strokes at the ends "
|
||||
"of letter strokes. These fonts are traditionally used in print media and "
|
||||
"can give a more formal, classic appearance. Many readers prefer serif fonts "
|
||||
"for long-form reading as they find them easier on the eyes."
|
||||
)
|
||||
for word in text2.split():
|
||||
para2.add_word(Word(word, default_font))
|
||||
blocks.append(para2)
|
||||
|
||||
# Section 3
|
||||
section3_heading = Heading(level=HeadingLevel.H2, style=heading_font)
|
||||
for word in "Monospace Font".split():
|
||||
section3_heading.add_word(Word(word, heading_font))
|
||||
blocks.append(section3_heading)
|
||||
|
||||
para3 = Paragraph(default_font)
|
||||
text3 = (
|
||||
"Monospace fonts like DejaVu Sans Mono have equal spacing between all characters. "
|
||||
"They are commonly used for displaying code, technical documentation, and typewriter-style "
|
||||
"text. While less common for general reading, some users prefer the uniform character "
|
||||
"spacing for certain types of content."
|
||||
)
|
||||
for word in text3.split():
|
||||
para3.add_word(Word(word, default_font))
|
||||
blocks.append(para3)
|
||||
|
||||
# Final paragraph
|
||||
conclusion = Paragraph(default_font)
|
||||
conclusion_text = (
|
||||
"The ability to switch fonts dynamically is a key feature of modern ereaders. "
|
||||
"It allows readers to customize their reading experience based on personal preference, "
|
||||
"lighting conditions, and content type. Try switching between the three font families "
|
||||
"to see which one you prefer for different types of reading."
|
||||
)
|
||||
for word in conclusion_text.split():
|
||||
conclusion.add_word(Word(word, default_font))
|
||||
blocks.append(conclusion)
|
||||
|
||||
return blocks
|
||||
|
||||
|
||||
def render_pages_with_different_fonts(manager, output_prefix="demo_11"):
|
||||
"""Render the same page with different font families"""
|
||||
|
||||
print("\nRendering pages with different font families...")
|
||||
print("=" * 70)
|
||||
|
||||
font_families = [
|
||||
(None, "Original (Sans)"),
|
||||
(BundledFont.SERIF, "Serif"),
|
||||
(BundledFont.MONOSPACE, "Monospace"),
|
||||
(BundledFont.SANS, "Sans (explicit)")
|
||||
]
|
||||
|
||||
images = []
|
||||
|
||||
for font_family, name in font_families:
|
||||
print(f"\nRendering with {name} font...")
|
||||
|
||||
# Switch font family
|
||||
manager.set_font_family(font_family)
|
||||
|
||||
# Get current page
|
||||
page = manager.get_current_page()
|
||||
|
||||
# Render to image
|
||||
image = page.render()
|
||||
filename = f"{output_prefix}_{name.lower().replace(' ', '_').replace('(', '').replace(')', '')}.png"
|
||||
image.save(filename)
|
||||
print(f" Saved: {filename}")
|
||||
|
||||
images.append((name, image))
|
||||
|
||||
return images
|
||||
|
||||
|
||||
def demonstrate_font_switching():
|
||||
"""Main demonstration function"""
|
||||
print("\n")
|
||||
print("=" * 70)
|
||||
print("Font Family Switching Demonstration")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# Create sample content
|
||||
print("Creating sample document...")
|
||||
blocks = create_sample_content()
|
||||
print(f" Created {len(blocks)} blocks")
|
||||
|
||||
# Initialize ereader manager
|
||||
print("\nInitializing ereader manager...")
|
||||
page_size = (600, 800)
|
||||
manager = create_ereader_manager(
|
||||
blocks,
|
||||
page_size,
|
||||
document_id="font_switching_demo"
|
||||
)
|
||||
print(f" Page size: {page_size[0]}x{page_size[1]}")
|
||||
print(f" Initial font family: {manager.get_font_family()}")
|
||||
|
||||
# Render pages with different fonts
|
||||
images = render_pages_with_different_fonts(manager)
|
||||
|
||||
# Show position info
|
||||
print("\nPosition information after font switches:")
|
||||
print(" " + "-" * 66)
|
||||
pos_info = manager.get_position_info()
|
||||
print(f" Current position: Block {pos_info['position']['block_index']}, "
|
||||
f"Word {pos_info['position']['word_index']}")
|
||||
print(f" Font family: {pos_info['font_family'] or 'Original'}")
|
||||
print(f" Font scale: {pos_info['font_scale']}")
|
||||
print(f" Reading progress: {pos_info['progress']:.1%}")
|
||||
|
||||
# Test navigation with font switching
|
||||
print("\nTesting navigation with font switching...")
|
||||
print(" " + "-" * 66)
|
||||
|
||||
# Reset to beginning
|
||||
manager.jump_to_position(manager.current_position.__class__())
|
||||
|
||||
# Advance a few pages with serif font
|
||||
manager.set_font_family(BundledFont.SERIF)
|
||||
print(f" Switched to SERIF font")
|
||||
|
||||
for i in range(3):
|
||||
next_page = manager.next_page()
|
||||
if next_page:
|
||||
print(f" Page {i+2}: Advanced successfully")
|
||||
|
||||
# Switch to monospace
|
||||
manager.set_font_family(BundledFont.MONOSPACE)
|
||||
print(f" Switched to MONOSPACE font")
|
||||
current_page = manager.get_current_page()
|
||||
print(f" Re-rendered current page with new font")
|
||||
|
||||
# Go back a page
|
||||
prev_page = manager.previous_page()
|
||||
if prev_page:
|
||||
print(f" Navigated back successfully")
|
||||
|
||||
# Cache statistics
|
||||
print("\nCache statistics:")
|
||||
print(" " + "-" * 66)
|
||||
stats = manager.get_cache_stats()
|
||||
for key, value in stats.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("Demo complete!")
|
||||
print()
|
||||
print("Key features demonstrated:")
|
||||
print(" ✓ Dynamic font family switching (Sans, Serif, Monospace)")
|
||||
print(" ✓ Position preservation across font changes")
|
||||
print(" ✓ Automatic cache invalidation on font change")
|
||||
print(" ✓ Navigation with different fonts")
|
||||
print(" ✓ Font family info in position tracking")
|
||||
print()
|
||||
print("The rendered pages show the same content in different font families.")
|
||||
print("Notice how the layout adapts while maintaining readability.")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demonstrate_font_switching()
|
||||
@@ -0,0 +1,252 @@
|
||||
"""
|
||||
Generate a demo image for README.md showing font family switching feature.
|
||||
|
||||
Creates a side-by-side comparison of the same content rendered in
|
||||
Sans, Serif, and Monospace fonts.
|
||||
"""
|
||||
|
||||
from pyWebLayout.abstract import Paragraph, Heading, Word
|
||||
from pyWebLayout.abstract.block import HeadingLevel
|
||||
from pyWebLayout.style import Font
|
||||
from pyWebLayout.style.fonts import BundledFont, FontWeight
|
||||
from pyWebLayout.style.page_style import PageStyle
|
||||
from pyWebLayout.layout.ereader_manager import create_ereader_manager
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
def create_demo_content():
|
||||
"""Create concise demo content that fits nicely on a small page"""
|
||||
blocks = []
|
||||
|
||||
# Title
|
||||
title_font = Font.from_family(BundledFont.SANS, font_size=28, weight=FontWeight.BOLD)
|
||||
title = Heading(level=HeadingLevel.H1, style=title_font)
|
||||
for word in "The Adventure Begins".split():
|
||||
title.add_word(Word(word, title_font))
|
||||
blocks.append(title)
|
||||
|
||||
# Paragraph
|
||||
body_font = Font.from_family(BundledFont.SANS, font_size=14)
|
||||
para = Paragraph(body_font)
|
||||
text = (
|
||||
"In the quiet village of Millbrook, young Emma discovered an ancient map "
|
||||
"hidden in her grandmother's attic. The parchment revealed a mysterious "
|
||||
"forest path marked with symbols she had never seen before. With courage "
|
||||
"in her heart and the map in her pocket, she set out at dawn to uncover "
|
||||
"the secrets that lay beyond the old oak trees."
|
||||
)
|
||||
for word in text.split():
|
||||
para.add_word(Word(word, body_font))
|
||||
blocks.append(para)
|
||||
|
||||
return blocks
|
||||
|
||||
|
||||
def render_with_font_family(blocks, page_size, font_family, family_name):
|
||||
"""Render a page with a specific font family"""
|
||||
manager = create_ereader_manager(
|
||||
blocks,
|
||||
page_size,
|
||||
document_id=f"demo_{family_name.lower()}"
|
||||
)
|
||||
|
||||
# Set font family (None means original/default)
|
||||
manager.set_font_family(font_family)
|
||||
|
||||
# Get the first page
|
||||
page = manager.get_current_page()
|
||||
return page.render()
|
||||
|
||||
|
||||
def create_comparison_image():
|
||||
"""Create a side-by-side comparison of all three font families"""
|
||||
|
||||
# Page size for each panel
|
||||
page_width = 400
|
||||
page_height = 300
|
||||
|
||||
# Create demo content
|
||||
print("Creating demo content...")
|
||||
blocks = create_demo_content()
|
||||
|
||||
# Render with each font family
|
||||
print("Rendering with Sans font...")
|
||||
sans_image = render_with_font_family(
|
||||
blocks, (page_width, page_height), BundledFont.SANS, "Sans"
|
||||
)
|
||||
|
||||
print("Rendering with Serif font...")
|
||||
serif_image = render_with_font_family(
|
||||
blocks, (page_width, page_height), BundledFont.SERIF, "Serif"
|
||||
)
|
||||
|
||||
print("Rendering with Monospace font...")
|
||||
mono_image = render_with_font_family(
|
||||
blocks, (page_width, page_height), BundledFont.MONOSPACE, "Monospace"
|
||||
)
|
||||
|
||||
# Create a composite image with all three side by side
|
||||
spacing = 20
|
||||
label_height = 30
|
||||
total_width = page_width * 3 + spacing * 4
|
||||
total_height = page_height + label_height + spacing * 2
|
||||
|
||||
composite = Image.new('RGB', (total_width, total_height), color='#f5f5f5')
|
||||
|
||||
# Paste the three images
|
||||
x_positions = [
|
||||
spacing,
|
||||
spacing * 2 + page_width,
|
||||
spacing * 3 + page_width * 2
|
||||
]
|
||||
|
||||
for img, x_pos in zip([sans_image, serif_image, mono_image], x_positions):
|
||||
composite.paste(img, (x_pos, label_height + spacing))
|
||||
|
||||
# Add labels
|
||||
draw = ImageDraw.Draw(composite)
|
||||
|
||||
# Try to use a nice font, fallback to default if not available
|
||||
try:
|
||||
label_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
|
||||
except:
|
||||
label_font = ImageFont.load_default()
|
||||
|
||||
labels = ["Sans-Serif", "Serif", "Monospace"]
|
||||
for label, x_pos in zip(labels, x_positions):
|
||||
# Calculate text position to center it
|
||||
bbox = draw.textbbox((0, 0), label, font=label_font)
|
||||
text_width = bbox[2] - bbox[0]
|
||||
text_x = x_pos + (page_width - text_width) // 2
|
||||
|
||||
draw.text((text_x, 5), label, fill='#333333', font=label_font)
|
||||
|
||||
# Save the image
|
||||
output_path = "docs/images/font_family_switching.png"
|
||||
composite.save(output_path, quality=95)
|
||||
print(f"\n✓ Saved demo image to: {output_path}")
|
||||
print(f" Image size: {total_width}x{total_height}")
|
||||
|
||||
return output_path
|
||||
|
||||
|
||||
def create_single_vertical_comparison():
|
||||
"""Create a vertical comparison that's better for README"""
|
||||
|
||||
# Page size for each panel
|
||||
page_width = 700
|
||||
page_height = 280
|
||||
|
||||
# Create demo content
|
||||
print("\nCreating vertical comparison for README...")
|
||||
blocks = create_demo_content()
|
||||
|
||||
# Render with each font family
|
||||
print(" Rendering Sans...")
|
||||
sans_image = render_with_font_family(
|
||||
blocks, (page_width, page_height), BundledFont.SANS, "Sans"
|
||||
)
|
||||
|
||||
print(" Rendering Serif...")
|
||||
serif_image = render_with_font_family(
|
||||
blocks, (page_width, page_height), BundledFont.SERIF, "Serif"
|
||||
)
|
||||
|
||||
print(" Rendering Monospace...")
|
||||
mono_image = render_with_font_family(
|
||||
blocks, (page_width, page_height), BundledFont.MONOSPACE, "Monospace"
|
||||
)
|
||||
|
||||
# Create a composite image stacked vertically
|
||||
spacing = 15
|
||||
label_width = 120
|
||||
total_width = page_width + label_width + spacing * 2
|
||||
total_height = page_height * 3 + spacing * 4
|
||||
|
||||
composite = Image.new('RGB', (total_width, total_height), color='#ffffff')
|
||||
|
||||
# Add a subtle border
|
||||
draw = ImageDraw.Draw(composite)
|
||||
draw.rectangle([(0, 0), (total_width-1, total_height-1)], outline='#e0e0e0', width=1)
|
||||
|
||||
# Paste the three images vertically
|
||||
y_positions = [
|
||||
spacing,
|
||||
spacing * 2 + page_height,
|
||||
spacing * 3 + page_height * 2
|
||||
]
|
||||
|
||||
images_data = [
|
||||
(sans_image, "Sans-Serif", "#4A90E2"),
|
||||
(serif_image, "Serif", "#E94B3C"),
|
||||
(mono_image, "Monospace", "#50C878")
|
||||
]
|
||||
|
||||
# Try to use a nice font
|
||||
try:
|
||||
label_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16)
|
||||
small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 11)
|
||||
except:
|
||||
label_font = ImageFont.load_default()
|
||||
small_font = ImageFont.load_default()
|
||||
|
||||
for (img, label, color), y_pos in zip(images_data, y_positions):
|
||||
# Paste the page image
|
||||
composite.paste(img, (label_width + spacing, y_pos))
|
||||
|
||||
# Draw label background
|
||||
draw.rectangle(
|
||||
[(spacing, y_pos + 10), (label_width, y_pos + 40)],
|
||||
fill=color
|
||||
)
|
||||
|
||||
# Draw label text
|
||||
draw.text(
|
||||
(spacing + 10, y_pos + 17),
|
||||
label,
|
||||
fill='#ffffff',
|
||||
font=label_font
|
||||
)
|
||||
|
||||
# Draw font description
|
||||
descriptions = {
|
||||
"Sans-Serif": "Clean & Modern",
|
||||
"Serif": "Classic & Formal",
|
||||
"Monospace": "Code & Technical"
|
||||
}
|
||||
draw.text(
|
||||
(spacing + 5, y_pos + 50),
|
||||
descriptions[label],
|
||||
fill='#666666',
|
||||
font=small_font
|
||||
)
|
||||
|
||||
# Save the image
|
||||
output_path = "docs/images/font_family_switching_vertical.png"
|
||||
composite.save(output_path, quality=95)
|
||||
print(f" ✓ Saved: {output_path}")
|
||||
print(f" Size: {total_width}x{total_height}")
|
||||
|
||||
return output_path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 70)
|
||||
print("Generating README Demo Images")
|
||||
print("=" * 70)
|
||||
|
||||
# Create both versions
|
||||
horizontal_path = create_comparison_image()
|
||||
vertical_path = create_single_vertical_comparison()
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("Demo images generated successfully!")
|
||||
print("=" * 70)
|
||||
print(f"\nHorizontal comparison: {horizontal_path}")
|
||||
print(f"Vertical comparison: {vertical_path}")
|
||||
print("\nRecommended for README: vertical version")
|
||||
print("\nMarkdown snippet:")
|
||||
print("```markdown")
|
||||
print("")
|
||||
print("```")
|
||||
print()
|
||||
Reference in New Issue
Block a user