Skip to main content

text_on_path 0.1.1 copy "text_on_path: ^0.1.1" to clipboard
text_on_path: ^0.1.1 copied to clipboard

Text along any curve, a circle for a seal or any Path, with cursive scripts kept joined. Arabic, Persian and Urdu stay connected; Devanagari and Bengali keep their headline.

example/lib/main.dart

import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:text_on_path/text_on_path.dart';

import 'bend.dart';
import 'compare.dart';
import 'draw.dart';
import 'panel.dart';
import 'platform_io.dart' if (dart.library.js_interop) 'platform_web.dart' as platform;
import 'seal.dart';
import 'select.dart';
import 'zoom.dart';

/// With `--dart-define=CAPTURE=true` (not on the web), the app saves each tab as a PNG in its temporary directory and
/// quits.
const capture = bool.fromEnvironment('CAPTURE');

void main() => runApp(const TextOnPathDemo());

class TextOnPathDemo extends StatelessWidget {
  const TextOnPathDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'text_on_path',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: joinedColor, surface: paper),
        scaffoldBackgroundColor: paper,
        sliderTheme: const SliderThemeData(trackHeight: 2.5),
      ),
      home: const _Home(),
    );
  }
}

class _Home extends StatefulWidget {
  const _Home();

  @override
  State<_Home> createState() => _HomeState();
}

class _HomeState extends State<_Home> with SingleTickerProviderStateMixin {
  static const _pages = ['Bend', 'Compare', 'Draw', 'Seals', 'Zoom', 'Select'];
  late final _tabs = TabController(length: _pages.length, vsync: this);
  final _boundary = GlobalKey();

  @override
  void initState() {
    super.initState();
    if (capture && !kIsWeb) WidgetsBinding.instance.addPostFrameCallback((_) => _capture());
  }

  Future<void> _capture() async {
    final dir = platform.captureFolder();
    Future<void> save(String name) async {
      await Future<void>.delayed(const Duration(seconds: 3));
      final boundary = _boundary.currentContext!.findRenderObject()! as RenderRepaintBoundary;
      final image = await boundary.toImage(pixelRatio: ui.PlatformDispatcher.instance.views.first.devicePixelRatio);
      final png = await image.toByteData(format: ui.ImageByteFormat.png);
      platform.writeCapture('$dir/$name.png', png!.buffer.asUint8List());
    }

    await save('bend');
    _tabs.index = 1;
    await save('compare');
    compareScroll.jumpTo(compareScroll.position.maxScrollExtent);
    await save('compare_end');
    for (final (i, name) in [(2, 'draw'), (3, 'seals'), (5, 'select'), (4, 'zoom')]) {
      _tabs.index = i;
      await save(name);
    }
    // Zoomed in 8 times: one atlas, then the atlas drawn again for the zoom, then outlines.
    zoomInOnText(8);
    for (final rendering in [TextOnPathRendering.raster, TextOnPathRendering.auto, TextOnPathRendering.vector]) {
      zoomRendering.value = rendering;
      await save('zoom8_${rendering.name}');
    }
    platform.quit();
  }

  @override
  void dispose() {
    _tabs.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      key: _boundary,
      child: Scaffold(
        body: SafeArea(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Padding(
                padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      'text_on_path',
                      style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: ink),
                    ),
                    const SizedBox(height: 4),
                    ConstrainedBox(
                      constraints: const BoxConstraints(maxWidth: 1100),
                      child: const Text(
                        'Text along any curve, cursive joins kept closed. Turning each letter whole, as text-on-path '
                        'packages do, opens joins on the outside of a bend and overlaps them on the inside; bending '
                        'the whole line stretches the letters. text_on_path keeps letters rigid and bends only the '
                        'rows they join on.',
                        style: TextStyle(fontSize: 13, color: muted, height: 1.4),
                      ),
                    ),
                  ],
                ),
              ),
              TabBar(
                controller: _tabs,
                isScrollable: true,
                tabAlignment: TabAlignment.start,
                tabs: [for (final p in _pages) Tab(text: p)],
              ),
              Expanded(
                child: TabBarView(
                  controller: _tabs,
                  physics: const NeverScrollableScrollPhysics(),
                  children: const [BendPage(), ComparePage(), DrawPage(), SealPage(), ZoomPage(), SelectPage()],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
2
likes
160
points
474
downloads
screenshot

Documentation

API reference

Publisher

verified publisherraouf.dev

Weekly Downloads

Text along any curve, a circle for a seal or any Path, with cursive scripts kept joined. Arabic, Persian and Urdu stay connected; Devanagari and Bengali keep their headline.

Homepage
Repository (GitHub)
View/report issues

Topics

#text #typography #arabic #rtl #curved-text

License

Apache-2.0 (license)

Dependencies

flutter

More

Packages that depend on text_on_path