Skip to content

Latest commit

 

History

History
216 lines (179 loc) · 6.56 KB

PLAN.md

File metadata and controls

216 lines (179 loc) · 6.56 KB

PHP 8 Port Implementation Plan for Pretext

Overview

This document outlines the implementation plan for porting the TypeScript pretext text layout library to PHP 8.

Architecture

File Structure

src/
  Enum/
    SegmentBreakKind.php
  DTO/
    SegmentMetrics.php
    EngineProfile.php
    TextAnalysis.php
    MergedSegmentation.php
    PreparedText.php
    PreparedTextWithSegments.php
    LayoutCursor.php
    LayoutResult.php
    LayoutLine.php
    LayoutLineRange.php
    InternalLayoutLine.php
    LineBreakCursor.php
  Measurement/
    FontMeasurerInterface.php
    GdFontMeasurer.php
    StubFontMeasurer.php
  Analysis.php
  Bidi.php
  Measurement.php
  LineBreak.php
  Pretext.php
  PretextFactory.php
tests/
  Unit/
    AnalysisTest.php
    BidiTest.php
    MeasurementTest.php
    LineBreakTest.php
    PretextTest.php
    FontMeasurerTest.php
  Stub/
    TestFontMeasurer.php
  bootstrap.php
phpunit.xml
composer.json

Implementation Phases

Phase 1: Foundation (DTOs and Enums)

  • Create SegmentBreakKind enum (5 cases)
  • Create all DTO classes as readonly classes
  • Set up composer.json with required dependencies
  • Set up PHPUnit configuration

Phase 2: Core Logic Modules

  • Implement Analysis.php - Text segmentation and merging logic
  • Implement Bidi.php - Bidirectional text algorithm
  • Implement Measurement.php - Text measurement with injectable font measurer
  • Implement LineBreak.php - Line breaking algorithm

Phase 3: Public API

  • Implement Pretext.php - Main class with instance-based API
  • Implement PretextFactory.php - Static factory for convenience

Phase 4: Font Measurement

  • Implement FontMeasurerInterface
  • Implement StubFontMeasurer for testing
  • Implement GdFontMeasurer for production use with GD/FreeType

Phase 5: Test Suite

  • Port all TypeScript tests to PHPUnit
  • Implement test stubs matching the original behavior
  • Add edge case tests for PHP-specific behavior
  • Ensure 100% feature parity with original test suite

Key Technical Decisions

1. Character Encoding

  • Use mb_* functions for all string operations
  • Use mb_str_split($str, 1) for codepoint iteration
  • All internal strings are UTF-8

2. Sets → Associative Arrays

private const KINSOKU_START = ["\u{FF0C}" => true, ...];
// Usage: isset(self::KINSOKU_START[$ch])

3. Intl Segmenter → IntlBreakIterator

  • Word segmentation: IntlBreakIterator::createWordInstance()
  • Grapheme segmentation: IntlBreakIterator::createCharacterInstance()
  • isWordLike → getRuleStatus() !== IntlBreakIterator::WORD_NONE

4. WeakMap

  • PHP 8.0+ has native WeakMap support
  • Use for lineTextCaches keyed by PreparedTextWithSegments objects

5. Canvas → FontMeasurerInterface

  • Abstract font measurement behind interface
  • GdFontMeasurer uses imagettfbbox() for production
  • StubFontMeasurer mirrors the original test fake

6. EngineProfile

  • Always use server/headless profile (no browser detection)
  • lineFitEpsilon: 0.005
  • carryCJKAfterClosingQuote: false
  • preferPrefixWidthsForBreakableRuns: false
  • preferEarlySoftHyphenBreak: false

Dependencies

Required PHP Extensions

  • intl - ICU for word/grapheme segmentation, IntlChar
  • mbstring - Multibyte string functions
  • gd (optional, for production) - FreeType font measurement
  • PCRE2 with Unicode support (PHP 7.3+)

Composer Dependencies

  • phpunit/phpunit - Testing framework
  • phpstan/phpstan - Static analysis (optional)

Testing Strategy

Test Categories

  1. Unit Tests - Individual module testing
  2. Integration Tests - Full prepare → layout pipeline
  3. Locale Tests - Thai, Arabic, CJK, Devanagari, Myanmar
  4. Edge Cases - Empty strings, whitespace, special characters
  5. Performance Tests - Ensure O(n) complexity maintained

Test Data from Original

  • All 50+ test cases from layout.test.ts
  • Character width mapping from measureWidth()
  • Segment expectations for various languages

API Surface

Main Class: Pretext

class Pretext {
    public function __construct(FontMeasurerInterface $measurer);
    public function prepare(string $text, string $font): PreparedText;
    public function prepareWithSegments(string $text, string $font): PreparedTextWithSegments;
    public function layout(PreparedText $prepared, float $maxWidth, float $lineHeight): LayoutResult;
    public function layoutWithLines(...): LayoutLinesResult;
    public function walkLineRanges(...): int;
    public function layoutNextLine(...): ?LayoutLine;
    public function clearCache(): void;
    public function setLocale(?string $locale): void;
}

Static Factory: PretextFactory

class PretextFactory {
    public static function create(?FontMeasurerInterface $measurer = null): Pretext;
    public static function prepare(string $text, string $font): PreparedText;
    public static function layout(...): LayoutResult;
}

Implementation Notes

Analysis.php Complexity

  • Most complex module due to multiple merge passes
  • IntlBreakIterator API differs slightly from Intl.Segmenter
  • Careful with isWordLike detection logic

LineBreak.php Arithmetic

  • Pure arithmetic, most straightforward to port
  • Inner functions become closures with use (&$var)
  • Track both segment indices and grapheme indices

Bidi.php Codepoints

  • All bidi-relevant ranges are BMP (≤ U+08AC)
  • Safe to use mb_ord() which returns code points
  • segStarts tracked as codepoint offsets

Measurement.php Emoji Correction

  • Browser-specific DOM comparison not available
  • Emoji correction always returns 0 in PHP
  • Acceptable for server-side use

Validation Checklist

  • All TypeScript types mapped to PHP DTOs
  • All constants (Sets, regexes) ported
  • All public functions from layout.ts implemented
  • All merge functions from analysis.ts implemented
  • Bidi algorithm produces same levels
  • Line breaking matches original behavior
  • Test suite passes with StubFontMeasurer
  • No browser-specific dependencies in core logic
  • PHP 8.0+ features used appropriately (readonly, WeakMap, match)

Success Metrics

  1. Feature Parity: 100% of original test cases pass
  2. Performance: ≤2x slower than JS (acceptable for PHP)
  3. Memory: Efficient caching with WeakMap
  4. API: Clean, idiomatic PHP interface
  5. Documentation: Full PHPDoc coverage

Timeline

  • Phase 1 (DTOs/Enums): 30 minutes
  • Phase 2 (Core): 2 hours
  • Phase 3 (API): 30 minutes
  • Phase 4 (Fonts): 30 minutes
  • Phase 5 (Tests): 1 hour

Total Estimated Time: 4.5 hours