From fd77b89b708b685a6bcc91c30e5dca167fefb052 Mon Sep 17 00:00:00 2001 From: LokeshPalani Date: Wed, 20 Mar 2024 14:59:21 +0530 Subject: [PATCH 01/11] Removed the notes section and updated the content. --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index ffe8cdd9e..9e01768eb 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,10 @@ # Syncfusion Flutter Widgets -Syncfusion Flutter widgets libraries include high-quality UI widgets and file-format packages to help you create rich, high-quality applications for iOS, Android, and web from a single code base. +Syncfusion Flutter widgets libraries include high-quality UI widgets and file-format packages to help you create rich, high-quality applications for iOS, Android, and web from a single code base. Please find the [supported platforms](https://help.syncfusion.com/flutter/system-requirements#supported-platforms) for our Flutter widgets. **Disclaimer:** This is a commercial package. To use our packages, you need to have either the Syncfusion Commercial License or Syncfusion Community license. For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. -**Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. - From ea8a5ad4d5c355aa274cfdacc99db1b15ef3f94c Mon Sep 17 00:00:00 2001 From: LokeshPalani Date: Wed, 20 Mar 2024 15:39:03 +0530 Subject: [PATCH 02/11] Added missed theme file for charts and sparkcharts --- .../lib/src/charts/theme.dart | 373 +++++++++++++ .../lib/src/theme/spark_charts_theme.dart | 503 ++++++++++++++++++ 2 files changed, 876 insertions(+) create mode 100644 packages/syncfusion_flutter_charts/lib/src/charts/theme.dart create mode 100644 packages/syncfusion_flutter_core/lib/src/theme/spark_charts_theme.dart diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/theme.dart b/packages/syncfusion_flutter_charts/lib/src/charts/theme.dart new file mode 100644 index 000000000..2468af49d --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/theme.dart @@ -0,0 +1,373 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// Holds the value of [SfChartThemeData] color properties for +/// material 2 theme based on the brightness. +class SfChartThemeDataM2 extends SfChartThemeData { + /// Creating an argument constructor of SfChartThemeDataM2 class. + SfChartThemeDataM2(this.context); + + /// Specifies the build context of the chart widgets. + final BuildContext context; + + /// Specifies the material app color scheme based on the brightness. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + /// Specifies the material app theme data based on the brightness. + late final TextTheme textTheme = Theme.of(context).textTheme; + + @override + Color? get backgroundColor => Colors.transparent; + + List get palette => const [ + Color.fromRGBO(75, 135, 185, 1), + Color.fromRGBO(192, 108, 132, 1), + Color.fromRGBO(246, 114, 128, 1), + Color.fromRGBO(248, 177, 149, 1), + Color.fromRGBO(116, 180, 155, 1), + Color.fromRGBO(0, 168, 181, 1), + Color.fromRGBO(73, 76, 162, 1), + Color.fromRGBO(255, 205, 96, 1), + Color.fromRGBO(255, 240, 219, 1), + Color.fromRGBO(238, 238, 238, 1) + ]; + + @override + Color? get axisLabelColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(104, 104, 104, 1) + : const Color.fromRGBO(242, 242, 242, 1); + + @override + Color? get axisTitleColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(66, 66, 66, 1) + : const Color.fromRGBO(255, 255, 255, 1); + + @override + Color? get axisLineColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(181, 181, 181, 1) + : const Color.fromRGBO(101, 101, 101, 1); + + @override + Color? get majorGridLineColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(219, 219, 219, 1) + : const Color.fromRGBO(70, 74, 86, 1); + + @override + Color? get minorGridLineColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(234, 234, 234, 1) + : const Color.fromRGBO(70, 74, 86, 1); + + @override + Color? get majorTickLineColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(181, 181, 181, 1) + : const Color.fromRGBO(191, 191, 191, 1); + + @override + Color? get minorTickLineColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(214, 214, 214, 1) + : const Color.fromRGBO(150, 150, 150, 1); + + @override + Color? get titleTextColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(66, 66, 66, 1) + : const Color.fromRGBO(255, 255, 255, 1); + + @override + Color? get titleBackgroundColor => Colors.transparent; + + @override + Color? get legendTextColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(53, 53, 53, 1) + : const Color.fromRGBO(255, 255, 255, 1); + + @override + Color? get legendBackgroundColor => Colors.transparent; + + @override + Color? get legendTitleColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(66, 66, 66, 1) + : const Color.fromRGBO(255, 255, 255, 1); + + Color get markerFillColor => + colorScheme.brightness == Brightness.light ? Colors.white : Colors.black; + + Color get dataLabelBackgroundColor => + colorScheme.brightness == Brightness.light ? Colors.white : Colors.black; + + @override + Color? get plotAreaBackgroundColor => Colors.transparent; + + @override + Color? get plotAreaBorderColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(219, 219, 219, 1) + : const Color.fromRGBO(101, 101, 101, 1); + + @override + Color? get crosshairLineColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(79, 79, 79, 1) + : const Color.fromRGBO(255, 255, 255, 1); + + @override + Color? get crosshairBackgroundColor => + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(79, 79, 79, 1) + : const Color.fromRGBO(255, 255, 255, 1); + + @override + Color? get crosshairLabelColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(0, 0, 0, 1); + + @override + Color? get tooltipColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(0, 8, 22, 1) + : const Color.fromRGBO(255, 255, 255, 1); + + @override + Color? get tooltipLabelColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(0, 0, 0, 1); + + @override + Color? get tooltipSeparatorColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(150, 150, 150, 1); + + @override + Color? get selectionRectColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(41, 171, 226, 0.1) + : const Color.fromRGBO(255, 217, 57, 0.3); + + @override + Color? get selectionRectBorderColor => + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(41, 171, 226, 1) + : const Color.fromRGBO(255, 255, 255, 1); + + @override + Color? get selectionTooltipConnectorLineColor => + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(79, 79, 79, 1) + : const Color.fromRGBO(150, 150, 150, 1); + + @override + Color? get waterfallConnectorLineColor => + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 1) + : const Color.fromRGBO(255, 255, 255, 1); + + @override + TextStyle? get titleTextStyle => textTheme.bodyMedium?.copyWith(fontSize: 15); + + @override + TextStyle? get axisTitleTextStyle => + textTheme.bodyMedium?.copyWith(fontSize: 15); + + @override + TextStyle? get axisLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get axisMultiLevelLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get plotBandLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get legendTitleTextStyle => textTheme.bodySmall; + + @override + TextStyle? get legendTextStyle => textTheme.bodySmall?.copyWith(fontSize: 13); + + @override + TextStyle? get dataLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get tooltipTextStyle => textTheme.bodySmall; + + @override + TextStyle? get trackballTextStyle => textTheme.bodySmall; + + @override + TextStyle? get crosshairTextStyle => textTheme.bodySmall; + + @override + TextStyle? get selectionZoomingTooltipTextStyle => textTheme.bodySmall; +} + +/// Holds the value of [SfChartThemeData] color properties for material 3 +/// theme based on the brightness. +class SfChartThemeDataM3 extends SfChartThemeData { + /// Creating an argument constructor of SfChartThemeDataM3 class. + SfChartThemeDataM3(this.context); + + /// Specifies the build context of the chart widgets. + final BuildContext context; + + /// Specifies the material app color scheme based on the brightness. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + /// Specifies the material app theme data based on the brightness. + late final TextTheme textTheme = Theme.of(context).textTheme; + + @override + Color? get backgroundColor => Colors.transparent; + + List get palette => colorScheme.brightness == Brightness.light + ? const [ + Color.fromRGBO(6, 174, 224, 1), + Color.fromRGBO(99, 85, 199, 1), + Color.fromRGBO(49, 90, 116, 1), + Color.fromRGBO(255, 180, 0, 1), + Color.fromRGBO(150, 60, 112, 1), + Color.fromRGBO(33, 150, 245, 1), + Color.fromRGBO(71, 59, 137, 1), + Color.fromRGBO(236, 92, 123, 1), + Color.fromRGBO(59, 163, 26, 1), + Color.fromRGBO(236, 131, 23, 1) + ] + : const [ + Color.fromRGBO(255, 245, 0, 1), + Color.fromRGBO(51, 182, 119, 1), + Color.fromRGBO(218, 150, 70, 1), + Color.fromRGBO(201, 88, 142, 1), + Color.fromRGBO(77, 170, 255, 1), + Color.fromRGBO(255, 157, 69, 1), + Color.fromRGBO(178, 243, 46, 1), + Color.fromRGBO(185, 60, 228, 1), + Color.fromRGBO(48, 167, 6, 1), + Color.fromRGBO(207, 142, 14, 1) + ]; + + @override + Color? get axisLabelColor => colorScheme.onSurfaceVariant; + + @override + Color? get axisTitleColor => colorScheme.onSurfaceVariant; + + @override + Color? get axisLineColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(202, 196, 208, 1) + : const Color.fromRGBO(73, 69, 79, 1); + + @override + Color? get majorGridLineColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(231, 224, 236, 1) + : const Color.fromRGBO(54, 50, 59, 1); + + // TODO(Praveen): Need to update color + @override + Color? get minorGridLineColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(234, 234, 234, 1) + : const Color.fromRGBO(70, 74, 86, 1); + + @override + Color? get majorTickLineColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(202, 196, 208, 1) + : const Color.fromRGBO(73, 69, 79, 1); + + // TODO(Praveen): Need to update color + @override + Color? get minorTickLineColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(214, 214, 214, 1) + : const Color.fromRGBO(150, 150, 150, 1); + + @override + Color? get titleTextColor => colorScheme.onSurfaceVariant; + + @override + Color? get titleBackgroundColor => Colors.transparent; + + @override + Color? get legendTextColor => colorScheme.onSurfaceVariant; + + @override + Color? get legendBackgroundColor => Colors.transparent; + + @override + Color? get legendTitleColor => colorScheme.onSurfaceVariant; + + Color get markerFillColor => + colorScheme.brightness == Brightness.light ? Colors.white : Colors.black; + + Color get dataLabelBackgroundColor => + colorScheme.brightness == Brightness.light ? Colors.white : Colors.black; + + @override + Color? get plotAreaBackgroundColor => Colors.transparent; + + @override + Color? get plotAreaBorderColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(231, 224, 236, 1) + : const Color.fromRGBO(54, 50, 59, 1); + + @override + Color? get crosshairLineColor => colorScheme.onSurfaceVariant; + + @override + Color? get crosshairBackgroundColor => colorScheme.inverseSurface; + + @override + Color? get crosshairLabelColor => colorScheme.onInverseSurface; + + @override + Color? get tooltipColor => colorScheme.inverseSurface; + + @override + Color? get tooltipLabelColor => colorScheme.onInverseSurface; + + @override + Color? get tooltipSeparatorColor => colorScheme.onInverseSurface; + + @override + Color? get selectionRectColor => colorScheme.brightness == Brightness.light + ? colorScheme.primary.withOpacity(0.1) + : colorScheme.primary.withOpacity(0.3); + + @override + Color? get selectionRectBorderColor => colorScheme.primary; + + @override + Color? get selectionTooltipConnectorLineColor => colorScheme.onSurfaceVariant; + + @override + Color? get waterfallConnectorLineColor => + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 1) + : const Color.fromRGBO(255, 255, 255, 1); + + @override + TextStyle? get titleTextStyle => textTheme.bodyMedium!.copyWith(fontSize: 16); + + @override + TextStyle? get axisTitleTextStyle => textTheme.bodyMedium; + + @override + TextStyle? get axisLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get axisMultiLevelLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get plotBandLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get legendTitleTextStyle => textTheme.bodySmall; + + @override + TextStyle? get legendTextStyle => textTheme.bodySmall; + + @override + TextStyle? get dataLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get tooltipTextStyle => textTheme.bodySmall; + + @override + TextStyle? get trackballTextStyle => textTheme.bodySmall; + + @override + TextStyle? get crosshairTextStyle => textTheme.bodySmall; + + @override + TextStyle? get selectionZoomingTooltipTextStyle => textTheme.bodySmall; +} diff --git a/packages/syncfusion_flutter_core/lib/src/theme/spark_charts_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/spark_charts_theme.dart new file mode 100644 index 000000000..cfde765bc --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/src/theme/spark_charts_theme.dart @@ -0,0 +1,503 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import '../../theme.dart'; + +/// Applies a theme to descendant Syncfusion spark chart widgets. +/// +/// To obtain the current theme, use [SfSparkChartTheme.of]. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfSparkChartTheme( +/// data: SfSparkChartThemeData( +/// brightness: Brightness.light +/// ), +/// child: SfSparkLineChart(), +/// ), +/// ); +/// } +/// ``` +/// +/// See also: +/// +/// * [SfTheme](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfTheme-class.html) +/// and [SfThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfThemeData-class.html), +/// for customizing the visual appearance of the spark chart widgets. +/// +class SfSparkChartTheme extends InheritedTheme { + /// Initialize the class of SfSparkChartTheme + const SfSparkChartTheme({Key? key, required this.data, required this.child}) + : super(key: key, child: child); + + /// Specifies the color and typography values for descendant + /// spark chart widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfSparkChartTheme( + /// data: SfSparkChartThemeData( + /// brightness: Brightness.light + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ); + /// } + /// ``` + + final SfSparkChartThemeData data; + + /// Specifies a widget that can hold single child. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfSparkChartTheme( + /// data: SfSparkChartThemeData( + /// brightness: Brightness.light + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ); + /// } + /// ``` + @override + final Widget child; + + /// The data from the closest [SfSparkChartTheme] instance + /// that encloses the given context. + /// + /// Defaults to [SfSparkChartTheme.SparkChartThemeData] + /// if there is no [SfSparkChartTheme] in the given build context. + static SfSparkChartThemeData of(BuildContext context) { + final SfSparkChartTheme? sfSparkChartTheme = + context.dependOnInheritedWidgetOfExactType(); + return sfSparkChartTheme?.data ?? SfTheme.of(context).sparkChartThemeData; + } + + @override + bool updateShouldNotify(SfSparkChartTheme oldWidget) => + data != oldWidget.data; + + @override + Widget wrap(BuildContext context, Widget child) { + final SfSparkChartTheme? ancestorTheme = + context.findAncestorWidgetOfExactType(); + return identical(this, ancestorTheme) + ? child + : SfSparkChartTheme(data: data, child: child); + } +} + +/// Holds the color and typography values for a [SfSparkChartTheme]. +/// Applies a theme to descendant Syncfusion spark charts widgets. +/// +/// To obtain the current theme, use [SfSparkChartTheme.of]. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfSparkChartTheme( +/// data: SfSparkChartThemeData( +/// brightness: Brightness.light +/// ), +/// child: SfSparkLineChart(), +/// ), +/// ); +/// } +/// ``` +/// +/// See also: +/// +/// * [SfTheme](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfTheme-class.html) +/// and [SfThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfThemeData-class.html), +/// for customizing the visual appearance of the spark chart widgets. +/// +@immutable +class SfSparkChartThemeData with Diagnosticable { + /// Initialize the Sfspark chart theme data + const SfSparkChartThemeData({ + this.backgroundColor, + this.color, + this.axisLineColor, + this.markerFillColor, + this.dataLabelBackgroundColor, + this.tooltipColor, + this.trackballLineColor, + this.tooltipLabelColor, + this.dataLabelTextStyle, + this.trackballTextStyle, + }); + + /// Create a [SfSparkChartThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfSparkChartThemeData] constructor. + factory SfSparkChartThemeData.raw({ + Brightness? brightness, + Color? backgroundColor, + Color? color, + Color? axisLineColor, + Color? markerFillColor, + Color? dataLabelBackgroundColor, + Color? tooltipColor, + Color? trackballLineColor, + Color? tooltipLabelColor, + TextStyle? dataLabelTextStyle, + TextStyle? trackballTextStyle, + }) { + brightness = brightness ?? Brightness.light; + + return SfSparkChartThemeData( + backgroundColor: backgroundColor, + color: color, + axisLineColor: axisLineColor, + markerFillColor: markerFillColor, + dataLabelBackgroundColor: dataLabelBackgroundColor, + tooltipColor: tooltipColor, + trackballLineColor: trackballLineColor, + tooltipLabelColor: tooltipLabelColor, + dataLabelTextStyle: dataLabelTextStyle, + trackballTextStyle: trackballTextStyle, + ); + } + + /// Specifies the background color of spark chart widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// SparkChartThemeData: SfSparkChartThemeData( + /// backgroundColor: Colors.yellow + /// ), + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + ///} + /// ``` + final Color? backgroundColor; + + /// Specifies the color for axis line. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// axisLineColor: Colors.blue + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? axisLineColor; + + /// Specifies the color of the spark chart. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// color: Colors.blue + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? color; + + /// Specifies the color for chart marker. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// markerFillColor: Colors.red + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? markerFillColor; + + /// Specifies the background color for data labels. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// dataLabelBackgroundColor: Colors.red + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? dataLabelBackgroundColor; + + /// Specifies the color of the tooltip. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// tooltipColor: Colors.teal + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? tooltipColor; + + /// Specifies the tooltip line color. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// trackballLineColor: Colors.red + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? trackballLineColor; + + /// Specifies the text color of the tooltip. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// tooltipLabelColor: Colors.amber + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? tooltipLabelColor; + + /// Specifies the data label text style for spark chart label text. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// SparkChartThemeData: SfSparkChartThemeData( + /// dataLabelTextStyle: TextStyle(color: Colors.blue) + /// ), + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + final TextStyle? dataLabelTextStyle; + + /// Specifies the trackball text style for spark chart label text. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// SparkChartThemeData: SfSparkChartThemeData( + /// trackballTextStyle: TextStyle(color: Colors.blue) + /// ), + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + final TextStyle? trackballTextStyle; + + /// Creates a copy of this spark chart theme data object with the + /// matching fields + /// replaced with the non-null parameter values. + SfSparkChartThemeData copyWith({ + Brightness? brightness, + Color? backgroundColor, + Color? color, + Color? axisLineColor, + Color? markerFillColor, + Color? dataLabelBackgroundColor, + Color? tooltipColor, + Color? trackballLineColor, + Color? tooltipLabelColor, + TextStyle? dataLabelTextStyle, + TextStyle? trackballTextStyle, + }) { + return SfSparkChartThemeData.raw( + brightness: brightness, + backgroundColor: backgroundColor ?? this.backgroundColor, + color: color ?? this.color, + axisLineColor: axisLineColor ?? this.axisLineColor, + markerFillColor: markerFillColor ?? this.markerFillColor, + dataLabelBackgroundColor: + dataLabelBackgroundColor ?? this.dataLabelBackgroundColor, + tooltipColor: tooltipColor ?? this.tooltipColor, + trackballLineColor: trackballLineColor ?? this.trackballLineColor, + tooltipLabelColor: tooltipLabelColor ?? this.tooltipLabelColor, + dataLabelTextStyle: dataLabelTextStyle ?? this.dataLabelTextStyle, + trackballTextStyle: trackballTextStyle ?? this.trackballTextStyle); + } + + /// Returns the spark chart theme data + static SfSparkChartThemeData? lerp( + SfSparkChartThemeData? a, SfSparkChartThemeData? b, double t) { + if (a == null && b == null) { + return null; + } + return SfSparkChartThemeData( + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), + color: Color.lerp(a.color, b.color, t), + axisLineColor: Color.lerp(a.axisLineColor, b.axisLineColor, t), + markerFillColor: Color.lerp(a.markerFillColor, b.markerFillColor, t), + dataLabelBackgroundColor: + Color.lerp(a.dataLabelBackgroundColor, b.dataLabelBackgroundColor, t), + tooltipColor: Color.lerp(a.tooltipColor, b.tooltipColor, t), + trackballLineColor: + Color.lerp(a.trackballLineColor, b.trackballLineColor, t), + tooltipLabelColor: + Color.lerp(a.tooltipLabelColor, b.tooltipLabelColor, t), + dataLabelTextStyle: + TextStyle.lerp(a.dataLabelTextStyle, b.dataLabelTextStyle, t), + trackballTextStyle: + TextStyle.lerp(a.trackballTextStyle, b.trackballTextStyle, t), + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is SfSparkChartThemeData && + other.backgroundColor == backgroundColor && + other.color == color && + other.axisLineColor == axisLineColor && + other.markerFillColor == markerFillColor && + other.dataLabelBackgroundColor == dataLabelBackgroundColor && + other.tooltipColor == tooltipColor && + other.trackballLineColor == trackballLineColor && + other.tooltipLabelColor == tooltipLabelColor && + other.dataLabelTextStyle == dataLabelTextStyle && + other.trackballTextStyle == trackballTextStyle; + } + + @override + int get hashCode { + final List values = [ + backgroundColor, + color, + axisLineColor, + markerFillColor, + dataLabelBackgroundColor, + tooltipColor, + trackballLineColor, + tooltipLabelColor, + dataLabelTextStyle, + trackballTextStyle, + ]; + return Object.hashAll(values); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + const SfSparkChartThemeData defaultData = SfSparkChartThemeData(); + properties.add(ColorProperty('backgroundColor', backgroundColor, + defaultValue: defaultData.backgroundColor)); + properties + .add(ColorProperty('color', color, defaultValue: defaultData.color)); + properties.add(ColorProperty('axisLineColor', axisLineColor, + defaultValue: defaultData.axisLineColor)); + properties.add(ColorProperty('markerFillColor', markerFillColor, + defaultValue: defaultData.markerFillColor)); + properties.add(ColorProperty( + 'dataLabelBackgroundColor', dataLabelBackgroundColor, + defaultValue: defaultData.dataLabelBackgroundColor)); + properties.add(ColorProperty('tooltipColor', tooltipColor, + defaultValue: defaultData.tooltipColor)); + properties.add(ColorProperty('trackballLineColor', trackballLineColor, + defaultValue: defaultData.trackballLineColor)); + properties.add(ColorProperty('tooltipLabelColor', tooltipLabelColor, + defaultValue: defaultData.tooltipLabelColor)); + properties.add( + DiagnosticsProperty('dataLabelTextStyle', dataLabelTextStyle, + defaultValue: defaultData.dataLabelTextStyle), + ); + properties.add( + DiagnosticsProperty('trackballTextStyle', trackballTextStyle, + defaultValue: defaultData.trackballTextStyle), + ); + } +} From a655ac48e83778cf652ca69fe33421d3ecdf3f7f Mon Sep 17 00:00:00 2001 From: LokeshPalani Date: Wed, 20 Mar 2024 15:44:28 +0530 Subject: [PATCH 03/11] Added theme file for sliders --- .../lib/src/theme.dart | 565 ++++++++++++++++++ 1 file changed, 565 insertions(+) create mode 100644 packages/syncfusion_flutter_sliders/lib/src/theme.dart diff --git a/packages/syncfusion_flutter_sliders/lib/src/theme.dart b/packages/syncfusion_flutter_sliders/lib/src/theme.dart new file mode 100644 index 000000000..b4df149ec --- /dev/null +++ b/packages/syncfusion_flutter_sliders/lib/src/theme.dart @@ -0,0 +1,565 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:syncfusion_flutter_core/theme.dart'; + +///SfSLiderThemeData gives the color to [SfSlider] +///when useMaterial 3 is false +///It gets the [context] as parameter in constructors +///[SfSliderThemeDataM2] override the properties +///[SfSliderThemeDataM2] get the color of properties. +///return SfSliderThemeData() as SfSliderThemeDataM2 in matrial 2; + +class SfSliderThemeDataM2 extends SfSliderThemeData { + ///context is parameter of the constructor. + SfSliderThemeDataM2(this.context); + + ///[context] refers to the current state of the widget. + final BuildContext context; + + ///Theme.of(context) retrives the ThemeData + ///[colorScheme] is a variable along with + /// late and final keyword + /// to obtain the theme of the context from [ColorScheme]. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color? get activeTrackColor => colorScheme.primary; + + @override + Color? get inactiveTrackColor => colorScheme.primary.withOpacity(0.24); + + @override + Color? get disabledActiveTrackColor => + colorScheme.onSurface.withOpacity(0.32); + + @override + Color? get disabledInactiveTrackColor => + colorScheme.onSurface.withOpacity(0.12); + + @override + Color? get thumbColor => colorScheme.primary; + + @override + Color? get overlayColor => colorScheme.primary.withOpacity(0.12); + + @override + Color? get disabledThumbColor => Color.alphaBlend( + colorScheme.onSurface.withOpacity(0.38), colorScheme.surface); + + @override + Color? get activeTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get inactiveTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get activeMinorTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get inactiveMinorTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get activeDividerColor => colorScheme.onPrimary.withOpacity(0.54); + + @override + Color? get inactiveDividerColor => colorScheme.primary.withOpacity(0.54); + + @override + Color? get disabledActiveDividerColor => + colorScheme.onPrimary.withOpacity(0.12); + + @override + Color? get disabledInactiveDividerColor => + colorScheme.onSurface.withOpacity(0.12); + + @override + Color? get disabledActiveTickColor => colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get disabledInactiveTickColor => + colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get disabledActiveMinorTickColor => + colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get disabledInactiveMinorTickColor => + colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get tooltipBackgroundColor => + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(97, 97, 97, 1) + : const Color.fromRGBO(224, 224, 224, 1); +} + +///SfSLiderThemeData gives the color to [SfSlider] +///when useMaterial 3 is true +///It gets the [context] as parameter in constructors +///[SfSliderThemeDataM3] override the properties +///[SfSliderThemeDataM3] get the color of properties. +/// +///return SfSliderThemeData() as SfSliderThemeDataM3; + +class SfSliderThemeDataM3 extends SfSliderThemeData { + ///context is parameter of the constructor. + SfSliderThemeDataM3(this.context); + + ///context refers to the current state of the widget. + final BuildContext context; + + ///Theme.of(context) retrives the ThemeData + ///[colorScheme] is a variable along with + /// late and final keyword to obtain the theme of the context + /// from [ColorScheme]. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color? get activeTrackColor => colorScheme.primary; + + @override + Color? get inactiveTrackColor => colorScheme.surfaceVariant; + + @override + Color? get disabledActiveTrackColor => colorScheme.onSurfaceVariant; + + @override + Color? get disabledInactiveTrackColor => + colorScheme.onSurface.withOpacity(0.12); + + @override + Color? get disabledThumbColor => Color.alphaBlend( + colorScheme.onSurface.withOpacity(0.38), colorScheme.surface); + + @override + Color? get activeTickColor => colorScheme.outlineVariant; + + @override + Color? get inactiveTickColor => colorScheme.outlineVariant; + + @override + Color? get activeMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get inactiveMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get thumbColor => colorScheme.primary; + + @override + Color? get overlayColor => colorScheme.primary.withOpacity(0.08); + + @override + Color? get activeDividerColor => colorScheme.onPrimary.withOpacity(0.38); + + @override + Color? get inactiveDividerColor => + colorScheme.onSurfaceVariant.withOpacity(0.38); + + @override + Color? get disabledActiveDividerColor => + colorScheme.onSurfaceVariant.withOpacity(0.38); + + @override + Color? get disabledInactiveDividerColor => + colorScheme.onSurfaceVariant.withOpacity(0.38); + + @override + Color? get disabledActiveTickColor => colorScheme.outlineVariant; + + @override + Color? get disabledInactiveTickColor => colorScheme.outlineVariant; + + @override + Color? get disabledActiveMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get disabledInactiveMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get tooltipBackgroundColor => colorScheme.primary; +} + +///SfRangeSLiderThemeData gives the color to [SfRangeSlider] +///when useMaterial 3 is false +///It gets the [context] as parameter in constructors +///[SfRangeSliderThemeDataM2] override the properties +///[SfRangeSliderThemeDataM2] get the color of properties. +///return SfRangeSliderThemeData() as SfRangeSliderThemeDataM2; +class SfRangeSliderThemeDataM2 extends SfRangeSliderThemeData { + ///context is parameter of the constructor. + SfRangeSliderThemeDataM2(this.context); + + ///[context] refers to the current state of the widget. + final BuildContext context; + + ///Theme.of(context) retrives the ThemeData + ///[colorScheme] is a variable along with + /// late and final keyword + /// to obtain the theme of the context from [ColorScheme]. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color? get activeTrackColor => colorScheme.primary; + + @override + Color? get inactiveTrackColor => colorScheme.primary.withOpacity(0.24); + + @override + Color? get disabledActiveTrackColor => + colorScheme.onSurface.withOpacity(0.32); + + @override + Color? get disabledInactiveTrackColor => + colorScheme.onSurface.withOpacity(0.12); + + @override + Color? get thumbColor => colorScheme.primary; + + @override + Color? get disabledThumbColor => Color.alphaBlend( + colorScheme.onSurface.withOpacity(0.38), colorScheme.surface); + + @override + Color? get activeTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get inactiveTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get activeMinorTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get inactiveMinorTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get overlayColor => colorScheme.primary.withOpacity(0.12); + + @override + Color? get activeDividerColor => colorScheme.onPrimary.withOpacity(0.54); + + @override + Color? get inactiveDividerColor => colorScheme.primary.withOpacity(0.54); + + @override + Color? get disabledActiveDividerColor => + colorScheme.onPrimary.withOpacity(0.12); + + @override + Color? get disabledInactiveDividerColor => + colorScheme.onSurface.withOpacity(0.12); + + @override + Color? get disabledActiveTickColor => colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get disabledInactiveTickColor => + colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get disabledActiveMinorTickColor => + colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get disabledInactiveMinorTickColor => + colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get tooltipBackgroundColor => + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(97, 97, 97, 1) + : const Color.fromRGBO(224, 224, 224, 1); + + @override + Color? get overlappingTooltipStrokeColor => colorScheme.surface; + + @override + Color? get overlappingThumbStrokeColor => colorScheme.surface; +} + +///SfRangeSLiderThemeData gives the color to [SfRangeSlider] +/// when useMaterial 3 is true +///It gets the [context] as parameter in constructors +///[SfRangeSliderThemeDataM3] override the properties +///[SfRangeSliderThemeDataM3] get the color of properties. +/// +///return SfRangeSliderThemeData() as SfRangeSliderThemeDataM3; +class SfRangeSliderThemeDataM3 extends SfRangeSliderThemeData { + ///context is parameter of the constructor. + SfRangeSliderThemeDataM3(this.context); + + ///context refers to the current state of the widget. + final BuildContext context; + + ///Theme.of(context) retrives the ThemeData + ///[colorScheme] is a variable along with + /// late and final keyword to obtain the theme of the context + /// from [ColorScheme]. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color? get activeTrackColor => colorScheme.primary; + + @override + Color? get inactiveTrackColor => colorScheme.surfaceVariant; + + @override + Color? get disabledActiveTrackColor => colorScheme.onSurfaceVariant; + + @override + Color? get disabledInactiveTrackColor => + colorScheme.onSurface.withOpacity(0.12); + + @override + Color? get disabledThumbColor => Color.alphaBlend( + colorScheme.onSurface.withOpacity(0.38), colorScheme.surface); + + @override + Color? get activeTickColor => colorScheme.outlineVariant; + + @override + Color? get inactiveTickColor => colorScheme.outlineVariant; + + @override + Color? get activeMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get inactiveMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get thumbColor => colorScheme.primary; + + @override + Color? get overlayColor => colorScheme.primary.withOpacity(0.08); + + @override + Color? get activeDividerColor => colorScheme.onPrimary.withOpacity(0.38); + + @override + Color? get inactiveDividerColor => + colorScheme.onSurfaceVariant.withOpacity(0.38); + + @override + Color? get disabledActiveDividerColor => + colorScheme.onSurfaceVariant.withOpacity(0.38); + + @override + Color? get disabledInactiveDividerColor => + colorScheme.onSurfaceVariant.withOpacity(0.38); + + @override + Color? get disabledActiveTickColor => colorScheme.outlineVariant; + + @override + Color? get disabledInactiveTickColor => colorScheme.outlineVariant; + + @override + Color? get disabledActiveMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get disabledInactiveMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get tooltipBackgroundColor => colorScheme.primary; + + @override + Color? get overlappingTooltipStrokeColor => colorScheme.surface; + + @override + Color? get overlappingThumbStrokeColor => colorScheme.surface; +} + +///SfRangeSelectorThemeData gives the color to [SfRangeSelector] +/// when useMaterial 3 is false +///It gets the [context] as parameter in constructors +///[SfRangeSelectorThemeDataM2] override the properties +///[SfRangeSelectorThemeDataM2] get the color of properties. +///return SfRangeSelectorThemeData() as SfRangeSelectorThemeDataM2; +class SfRangeSelectorThemeDataM2 extends SfRangeSelectorThemeData { + ///context is parameter of the constructor. + SfRangeSelectorThemeDataM2(this.context); + + ///[context] refers to the current state of the widget. + final BuildContext context; + + ///Theme.of(context) retrives the ThemeData + ///[colorScheme] is a variable along with + /// late and final keyword + /// to obtain the theme of the context from [ColorScheme]. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color? get activeTrackColor => colorScheme.primary; + + @override + Color? get inactiveTrackColor => colorScheme.primary.withOpacity(0.24); + + @override + Color? get disabledActiveTrackColor => + colorScheme.onSurface.withOpacity(0.32); + + @override + Color? get disabledInactiveTrackColor => + colorScheme.onSurface.withOpacity(0.12); + + @override + Color? get thumbColor => colorScheme.primary; + + @override + Color? get disabledThumbColor => Color.alphaBlend( + colorScheme.onSurface.withOpacity(0.38), colorScheme.surface); + + @override + Color? get activeTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get inactiveTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get activeMinorTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get inactiveMinorTickColor => colorScheme.onSurface.withOpacity(0.37); + + @override + Color? get overlayColor => colorScheme.primary.withOpacity(0.12); + + @override + Color? get activeDividerColor => colorScheme.onPrimary.withOpacity(0.54); + + @override + Color? get inactiveDividerColor => colorScheme.primary.withOpacity(0.54); + + @override + Color? get disabledActiveDividerColor => + colorScheme.onPrimary.withOpacity(0.12); + + @override + Color? get disabledInactiveDividerColor => + colorScheme.onSurface.withOpacity(0.12); + + @override + Color? get disabledActiveTickColor => colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get disabledInactiveTickColor => + colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get disabledActiveMinorTickColor => + colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get disabledInactiveMinorTickColor => + colorScheme.onSurface.withOpacity(0.24); + + @override + Color? get tooltipBackgroundColor => + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(97, 97, 97, 1) + : const Color.fromRGBO(224, 224, 224, 1); + + @override + Color? get activeRegionColor => Colors.transparent; + + @override + Color? get inactiveRegionColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1).withOpacity(0.75) + : const Color.fromRGBO(48, 48, 48, 1).withOpacity(0.75); +} + +///SfRangeSelectorThemeData gives the color to [SfRangeSelector] +///when useMaterial 3 is true +///It gets the [context] as parameter in constructors +///[SfRangeSelectorThemeDataM3] override the properties +///[SfRangeSelectorThemeDataM3] get the color of properties. +/// +///return SfRangeSelectorThemeData() as SfRangeSelectorThemeDataM3; +class SfRangeSelectorThemeDataM3 extends SfRangeSelectorThemeData { + ///context is parameter of the constructor. + SfRangeSelectorThemeDataM3(this.context); + + ///context refers to the current state of the widget. + final BuildContext context; + + ///Theme.of(context) retrives the ThemeData + ///[colorScheme] is a variable along with + /// late and final keyword to obtain the theme of the context + /// from [ColorScheme]. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color? get activeTrackColor => colorScheme.primary; + + @override + Color? get inactiveTrackColor => colorScheme.surfaceVariant; + + @override + Color? get disabledActiveTrackColor => colorScheme.onSurfaceVariant; + + @override + Color? get disabledInactiveTrackColor => + colorScheme.onSurface.withOpacity(0.12); + + @override + Color? get disabledThumbColor => Color.alphaBlend( + colorScheme.onSurface.withOpacity(0.38), colorScheme.surface); + + @override + Color? get activeTickColor => colorScheme.outlineVariant; + + @override + Color? get inactiveTickColor => colorScheme.outlineVariant; + + @override + Color? get activeMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get inactiveMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get thumbColor => colorScheme.primary; + + @override + Color? get overlayColor => colorScheme.primary.withOpacity(0.08); + + @override + Color? get activeDividerColor => colorScheme.onPrimary.withOpacity(0.38); + + @override + Color? get inactiveDividerColor => + colorScheme.onSurfaceVariant.withOpacity(0.38); + + @override + Color? get disabledActiveDividerColor => + colorScheme.onSurfaceVariant.withOpacity(0.38); + + @override + Color? get disabledInactiveDividerColor => + colorScheme.onSurfaceVariant.withOpacity(0.38); + + @override + Color? get disabledActiveTickColor => colorScheme.outlineVariant; + + @override + Color? get disabledInactiveTickColor => colorScheme.outlineVariant; + + @override + Color? get disabledActiveMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get disabledInactiveMinorTickColor => colorScheme.outlineVariant; + + @override + Color? get tooltipBackgroundColor => colorScheme.primary; + + @override + Color? get activeRegionColor => Colors.transparent; + + @override + Color? get inactiveRegionColor => colorScheme.scrim.withOpacity(0.32); +} From b45221109ab073feeef09d4435ed461d337598db Mon Sep 17 00:00:00 2001 From: LokeshPalani Date: Wed, 20 Mar 2024 15:51:57 +0530 Subject: [PATCH 04/11] Added missed theme file for maps --- .../lib/src/theme.dart | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 packages/syncfusion_flutter_maps/lib/src/theme.dart diff --git a/packages/syncfusion_flutter_maps/lib/src/theme.dart b/packages/syncfusion_flutter_maps/lib/src/theme.dart new file mode 100644 index 000000000..50bec1810 --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/theme.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// [SfMapsThemeDataM2] this class provides material2 themeData. +/// SfMapsThemeDataM2 class extends the 'SfMapsThemeData' class and customize +/// the appearance of a mapping component based on th color scheme obtained from +/// the provided [BuildContext]. +class SfMapsThemeDataM2 extends SfMapsThemeData { + /// This a constructor that takes a [BuildContext] as a parameter.This context + /// is used for obtaining the color scheme of the current theme. + SfMapsThemeDataM2(this.context); + + /// Property that stores the provided [BuildContext] + /// context is later used to obtain the color scheme. + final BuildContext context; + + /// A late-initialized property representing the color scheme obtained from + /// the current theme using the provided [BuildContext] + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + /// Specifies the sub layer color of the maps widgets. + Color get subLayerColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(198, 198, 198, 1) + : const Color.fromRGBO(71, 71, 71, 1); + + /// Specifies the sub layer stroke color of the maps widgets. + Color get subLayerStrokeColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(145, 145, 145, 1) + : const Color.fromRGBO(133, 133, 133, 1); + + /// Specifies the sub layer width of the maps widgets. + double get subLayerStrokeWidth => + colorScheme.brightness == Brightness.light ? 0.5 : 0.25; + + @override + Color? get layerColor => (colorScheme.brightness == Brightness.light + ? colorScheme.onSurface.withOpacity(0.11) + : colorScheme.onSurface.withOpacity(0.24)); + + @override + Color? get layerStrokeColor => (colorScheme.brightness == Brightness.light + ? colorScheme.onSurface.withOpacity(0.18) + : colorScheme.onSurface.withOpacity(0.43)); + + @override + Color? get markerIconColor => (colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(98, 0, 238, 1) + : const Color.fromRGBO(187, 134, 252, 1)); + + @override + Color? get bubbleColor => (colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(98, 0, 238, 0.5) + : const Color.fromRGBO(187, 134, 252, 0.8)); + + @override + Color? get bubbleStrokeColor => Colors.transparent; + + @override + Color? get selectionColor => (colorScheme.brightness == Brightness.light + ? colorScheme.onSurface.withOpacity(0.53) + : colorScheme.onSurface.withOpacity(0.85)); + + @override + Color? get selectionStrokeColor => (colorScheme.brightness == Brightness.light + ? colorScheme.onPrimary.withOpacity(0.29) + : colorScheme.surface.withOpacity(0.56)); + + @override + Color? get tooltipColor => (colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(117, 117, 117, 1) + : const Color.fromRGBO(245, 245, 245, 1)); + + @override + Color? get toggledItemColor => (colorScheme.brightness == Brightness.light + ? colorScheme.onPrimary + : colorScheme.onSurface.withOpacity(0.09)); + + @override + Color? get toggledItemStrokeColor => + (colorScheme.brightness == Brightness.light + ? colorScheme.onSurface.withOpacity(0.37) + : colorScheme.onSurface.withOpacity(0.17)); +} + +/// [SfMapsThemeDataM3] this class provides material3 themeData. +/// SfMapsThemeDataM2 class extends the 'SfMapsThemeData' class and customize +/// the appearance of a mapping component based on th color scheme obtained from +/// the provided [BuildContext]. +class SfMapsThemeDataM3 extends SfMapsThemeData { + /// This a constructor that takes a [BuildContext] as a parameter.This context + /// is used for obtaining the color scheme of the current theme. + SfMapsThemeDataM3(this.context); + + /// Property that stores the provided [BuildContext] + /// context is later used to obtain the color scheme. + final BuildContext context; + + /// A late-initialized property representing the color scheme obtained from + /// the current theme using the provided [BuildContext] + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + /// Specifies the sub layer color of the maps widgets. + Color get subLayerColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(198, 198, 198, 1) + : const Color.fromRGBO(71, 71, 71, 1); + + /// Specifies the sub layer stroke color of the maps widgets. + Color get subLayerStrokeColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(145, 145, 145, 1) + : const Color.fromRGBO(133, 133, 133, 1); + + /// Specifies the sub layer width of the maps widgets. + double get subLayerStrokeWidth => + colorScheme.brightness == Brightness.light ? 0.5 : 0.25; + + @override + Color? get layerColor => (colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(231, 224, 236, 1) + : const Color.fromRGBO(54, 50, 59, 1)); + + @override + Color? get layerStrokeColor => (colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(202, 196, 208, 1) + : const Color.fromRGBO(73, 69, 79, 1)); + + @override + Color? get markerIconColor => colorScheme.primary; + + @override + Color? get bubbleColor => (colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(255, 180, 0, 0.4) + : const Color.fromRGBO(201, 88, 142, 0.4)); + + @override + Color? get bubbleStrokeColor => const Color.fromRGBO(255, 255, 255, 0.4); + + @override + Color? get selectionColor => (colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(202, 196, 208, 1) + : const Color.fromRGBO(73, 69, 79, 1)); + + @override + Color? get selectionStrokeColor => (colorScheme.brightness == Brightness.light + ? colorScheme.onPrimary.withOpacity(0.29) + : const Color.fromRGBO(73, 69, 79, 1)); + + @override + Color? get tooltipColor => colorScheme.inverseSurface; + + @override + Color? get toggledItemColor => (colorScheme.brightness == Brightness.light + ? colorScheme.onPrimary + : colorScheme.onSurface.withOpacity(0.09)); + + @override + Color? get toggledItemStrokeColor => + (colorScheme.brightness == Brightness.light + ? colorScheme.onSurface.withOpacity(0.37) + : colorScheme.onSurface.withOpacity(0.17)); +} From 73e8651bda49887adc433b6d84f1559107e5c146 Mon Sep 17 00:00:00 2001 From: LokeshPalani Date: Wed, 20 Mar 2024 15:58:48 +0530 Subject: [PATCH 05/11] I have included missed theme file for calendar --- .../lib/src/calendar/theme.dart | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 packages/syncfusion_flutter_calendar/lib/src/calendar/theme.dart diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/theme.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/theme.dart new file mode 100644 index 000000000..76df07964 --- /dev/null +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/theme.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// [SfCalendarThemeDataM2] this class provides material2 themeData. +/// SfCalendarThemeDataM2 class extends the 'SfCalendarThemeData' class and +/// customize the appearance of a mapping component based on th color scheme +/// obtained from the provided [BuildContext]. +class SfCalendarThemeDataM2 extends SfCalendarThemeData { + /// This a constructor that takes a [BuildContext] as a parameter.This context + /// is used for obtaining the color scheme of the current theme. + SfCalendarThemeDataM2(this.context); + + /// Property that stores the provided [BuildContext] + /// context is later used to obtain the color scheme. + final BuildContext context; + + /// A late-initialized property representing the color scheme obtained from + /// the current theme using the provided [BuildContext] + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color? get backgroundColor => Colors.transparent; + + @override + Color? get headerBackgroundColor => Colors.transparent; + + @override + Color? get agendaBackgroundColor => Colors.transparent; + + @override + Color? get activeDatesBackgroundColor => Colors.transparent; + + @override + Color? get todayBackgroundColor => Colors.transparent; + + @override + Color? get trailingDatesBackgroundColor => Colors.transparent; + + @override + Color? get leadingDatesBackgroundColor => Colors.transparent; + + @override + Color? get viewHeaderBackgroundColor => Colors.transparent; + + @override + Color? get allDayPanelColor => Colors.transparent; + + @override + Color? get weekNumberBackgroundColor => + colorScheme.onSurface.withOpacity(0.04); + + @override + Color? get cellBorderColor => colorScheme.onSurface.withOpacity(0.16); + + @override + Color? get todayHighlightColor => colorScheme.primary; + + @override + Color? get selectionBorderColor => colorScheme.primary; +} + +/// [SfCalendarThemeDataM3] this class provides material3 themeData. +/// SfCalendarThemeDataM3 class extends the 'SfCalendarThemeData' class and +/// customize the appearance of a mapping component based on th color scheme +/// obtained from the provided [BuildContext]. +class SfCalendarThemeDataM3 extends SfCalendarThemeData { + /// This a constructor that takes a [BuildContext] as a parameter.This context + /// is used for obtaining the color scheme of the current theme. + SfCalendarThemeDataM3(this.context); + + /// Property that stores the provided [BuildContext] + /// context is later used to obtain the color scheme. + final BuildContext context; + + /// A late-initialized property representing the color scheme obtained from + /// the current theme using the provided [BuildContext] + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color? get backgroundColor => Colors.transparent; + + @override + Color? get headerBackgroundColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(247, 242, 251, 1) + : const Color.fromRGBO(37, 35, 42, 1); + + @override + Color? get agendaBackgroundColor => Colors.transparent; + + @override + Color? get activeDatesBackgroundColor => Colors.transparent; + + @override + Color? get todayBackgroundColor => Colors.transparent; + + @override + Color? get trailingDatesBackgroundColor => Colors.transparent; + + @override + Color? get leadingDatesBackgroundColor => Colors.transparent; + + @override + Color? get viewHeaderBackgroundColor => Colors.transparent; + + @override + Color? get allDayPanelColor => Colors.transparent; + + @override + Color? get weekNumberBackgroundColor => + colorScheme.onSurface.withOpacity(0.04); + + @override + Color? get cellBorderColor => colorScheme.outlineVariant; + + @override + Color? get todayHighlightColor => colorScheme.primary; + + @override + Color? get selectionBorderColor => colorScheme.primary; +} From 5c1d4dc578142bfbc9114d9bb09a01180e0711fa Mon Sep 17 00:00:00 2001 From: LokeshPalani Date: Wed, 20 Mar 2024 15:59:42 +0530 Subject: [PATCH 06/11] I have included missed theme file for datepicker --- .../lib/src/date_picker/theme.dart | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/theme.dart diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/theme.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/theme.dart new file mode 100644 index 000000000..4a12b6438 --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/theme.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// [SfDateRangePickerThemeDataM2] this class provides material2 themeData. +/// SfDateRangePickerThemeDataM2 class extends the 'SfDateRangePickerThemeData' +/// class and customize the appearance of a mapping component based on th color +/// scheme obtained from the provided [BuildContext]. +class SfDateRangePickerThemeDataM2 extends SfDateRangePickerThemeData { + /// This a constructor that takes a [BuildContext] as a parameter.This context + /// is used for obtaining the color scheme of the current theme. + SfDateRangePickerThemeDataM2(this.context); + + /// Property that stores the provided [BuildContext] + /// context is later used to obtain the color scheme. + final BuildContext context; + + /// A late-initialized property representing the color scheme obtained from + /// the current theme using the provided [BuildContext] + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color? get backgroundColor => Colors.transparent; + + @override + Color? get headerBackgroundColor => colorScheme.brightness == Brightness.light + ? Colors.white + : Colors.grey[850]; + + @override + Color? get viewHeaderBackgroundColor => Colors.transparent; + + @override + Color? get weekNumberBackgroundColor => + colorScheme.onSurface.withOpacity(0.08); + + @override + Color? get selectionColor => colorScheme.primary; + + @override + Color? get startRangeSelectionColor => colorScheme.primary; + + @override + Color? get rangeSelectionColor => colorScheme.primary.withOpacity(0.1); + + @override + Color? get endRangeSelectionColor => colorScheme.primary; + + @override + Color? get todayHighlightColor => colorScheme.primary; +} + +/// [SfDateRangePickerThemeDataM3] this class provides material3 themeData. +/// SfDateRangePickerThemeDataM3 class extends the 'SfDateRangePickerThemeData' +/// class and customize the appearance of a mapping component based on th color +/// scheme obtained from the provided [BuildContext]. +class SfDateRangePickerThemeDataM3 extends SfDateRangePickerThemeData { + /// This a constructor that takes a [BuildContext] as a parameter.This context + /// is used for obtaining the color scheme of the current theme. + SfDateRangePickerThemeDataM3(this.context); + + /// Property that stores the provided [BuildContext] + /// context is later used to obtain the color scheme. + final BuildContext context; + + /// A late-initialized property representing the color scheme obtained from + /// the current theme using the provided [BuildContext] + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color? get backgroundColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1); + + @override + Color? get headerBackgroundColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1); + + @override + Color? get viewHeaderBackgroundColor => Colors.transparent; + + @override + Color? get weekNumberBackgroundColor => colorScheme.surfaceVariant; + + @override + Color? get selectionColor => colorScheme.primary; + + @override + Color? get startRangeSelectionColor => colorScheme.primary; + + @override + Color? get rangeSelectionColor => colorScheme.primary.withOpacity(0.12); + + @override + Color? get endRangeSelectionColor => colorScheme.primary; + + @override + Color? get todayHighlightColor => colorScheme.primary; +} From 276c8088d3a718ae78ed7af58660c4539f26bed4 Mon Sep 17 00:00:00 2001 From: LokeshPalani Date: Wed, 20 Mar 2024 16:11:24 +0530 Subject: [PATCH 07/11] Added missed files in the pdf --- .../annotations/fdf_document.dart | 305 ++++ .../annotations/fdf_parser.dart | 392 +++++ .../annotations/json_document.dart | 1132 ++++++++++++++ .../annotations/json_parser.dart | 1227 +++++++++++++++ .../annotations/xfdf_parser.dart | 1351 +++++++++++++++++ 5 files changed, 4407 insertions(+) create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_document.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_parser.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_document.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_parser.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/xfdf_parser.dart diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_document.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_document.dart new file mode 100644 index 000000000..ba34662bd --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_document.dart @@ -0,0 +1,305 @@ +import 'dart:convert'; + +import '../../interfaces/pdf_interface.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../pages/pdf_page.dart'; +import '../primitives/pdf_array.dart'; +import '../primitives/pdf_boolean.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; + +/// Internal class +class FdfDocument { + /// Internal Consturctor + FdfDocument(this.dictionary, this.page); + + /// Internal field + late PdfDictionary dictionary; + + /// Internal field + late PdfPage page; + + /// Internal field + String _annotationID = ''; + + /// Internal method + Map exportAnnotations( + int currentID, List annotID, int pageIndex, bool hasAppearance) { + const String startObject = + '${PdfOperators.whiteSpace}0${PdfOperators.whiteSpace}${PdfOperators.obj}${PdfOperators.newLine}'; + const String endObject = + PdfOperators.newLine + PdfOperators.endobj + PdfOperators.newLine; + PdfDictionary dictionary = this.dictionary; + _annotationID = currentID.toString(); + final List exportData = []; + exportData.addAll(utf8.encode('$currentID$startObject<<')); + final Map subDictionaries = {}; + final List streamReferences = []; + annotID.add(_annotationID); + dictionary.items![PdfName('Page')] = PdfNumber(pageIndex); + Map exportDataDictionary = _getEntriesInDictionary( + subDictionaries, + streamReferences, + currentID, + dictionary, + hasAppearance); + exportData.addAll(exportDataDictionary['exportData'] as List); + currentID = exportDataDictionary['currentID'] as int; + dictionary.remove('Page'); + exportData.addAll(utf8.encode('>>$endObject')); + while (subDictionaries.isNotEmpty) { + final List keys = subDictionaries.keys.toList(); + for (final int key in keys) { + if (subDictionaries[key] is PdfDictionary) { + dictionary = subDictionaries[key]! as PdfDictionary; + if (dictionary.containsKey(PdfDictionaryProperties.type)) { + final IPdfPrimitive? name = + dictionary[PdfDictionaryProperties.type]; + if (name != null && + name is PdfName && + name.name == PdfDictionaryProperties.annot) { + annotID.add(key.toString()); + dictionary.items![PdfName('Page')] = PdfNumber(pageIndex); + } + } + exportData.addAll(utf8.encode('$key$startObject<<')); + exportDataDictionary = _getEntriesInDictionary(subDictionaries, + streamReferences, currentID, dictionary, hasAppearance); + exportData.addAll(exportDataDictionary['exportData'] as List); + currentID = exportDataDictionary['currentID'] as int; + if (dictionary.containsKey('Page')) { + dictionary.remove('Page'); + } + exportData.addAll(utf8.encode('>>')); + if (streamReferences.contains(key)) { + exportData.addAll(_appendStream(subDictionaries[key]!)); + } + exportData.addAll(utf8.encode(endObject)); + } else if (subDictionaries[key] is PdfName) { + final PdfName name = subDictionaries[key]! as PdfName; + exportData.addAll(utf8.encode('$key$startObject$name$endObject')); + } else if (subDictionaries[key] is PdfArray) { + final PdfArray array = subDictionaries[key]! as PdfArray; + exportData.addAll(utf8.encode('$key$startObject')); + final Map result = _appendArrayElements(array, + currentID, hasAppearance, subDictionaries, streamReferences); + exportData.addAll(result['exportData'] as List); + currentID = result['currentID'] as int; + exportData.addAll(utf8.encode(endObject)); + } else if (subDictionaries[key] is PdfBoolean) { + final PdfBoolean boolean = subDictionaries[key]! as PdfBoolean; + exportData.addAll(utf8.encode( + '$key$startObject${boolean.value! ? 'true' : 'false'}$endObject')); + } else if (subDictionaries[key] is PdfString) { + final PdfString data = subDictionaries[key]! as PdfString; + if (data.value != null) { + exportData.addAll(utf8.encode( + '$key$startObject(${_getFormattedStringFDF(data.value!)})$endObject')); + } + } + subDictionaries.remove(key); + } + } + currentID++; + return {'exportData': exportData, 'currentID': currentID}; + } + + /// Internal method + List _appendStream(IPdfPrimitive stream) { + final List streamData = []; + if (stream is PdfStream && + stream.dataStream != null && + stream.dataStream!.isNotEmpty) { + streamData.addAll(utf8.encode('stream${PdfOperators.newLine}')); + streamData.addAll(stream.dataStream!); + streamData.addAll(utf8.encode('${PdfOperators.newLine}endstream')); + } + return streamData; + } + + Map _getEntriesInDictionary( + Map dictionaries, + List streamReferences, + int currentID, + PdfDictionary dictionary, + bool hasAppearance) { + final List annotationData = []; + bool isStream = false; + final List keys = dictionary.items!.keys.toList(); + for (final PdfName? key in keys) { + if (!hasAppearance && key!.name == PdfDictionaryProperties.ap) { + continue; + } + if (key!.name != PdfDictionaryProperties.p) { + annotationData.addAll(utf8.encode(key.toString())); + } + if (key.name == 'Sound' || + key.name == PdfDictionaryProperties.f || + hasAppearance) { + isStream = true; + } + final IPdfPrimitive? primitive = dictionary[key]; + if (primitive is PdfString) { + if (primitive.value != null) { + annotationData.addAll( + utf8.encode('(${_getFormattedStringFDF(primitive.value!)})')); + } + } else if (primitive is PdfName) { + annotationData.addAll(utf8.encode(primitive.toString())); + } else if (primitive is PdfArray) { + final Map result = _appendArrayElements( + primitive, currentID, isStream, dictionaries, streamReferences); + annotationData.addAll(result['exportData'] as List); + currentID = result['currentID'] as int; + } else if (primitive is PdfNumber) { + annotationData.addAll(utf8.encode(' ${primitive.value!}')); + } else if (primitive is PdfBoolean) { + annotationData.addAll(utf8.encode(' ${primitive.value!}')); + } else if (primitive is PdfDictionary) { + annotationData.addAll(utf8.encode('<<')); + final Map data = _getEntriesInDictionary(dictionaries, + streamReferences, currentID, primitive, hasAppearance); + annotationData.addAll(data['exportData'] as List); + currentID = data['currentID'] as int; + annotationData.addAll(utf8.encode('>>')); + } else if (primitive is PdfReferenceHolder) { + if (PdfPageHelper.getHelper(page).document != null) { + final int pageNumber = + PdfPageHelper.getHelper(page).document!.pages.indexOf(page); + if (key.name == PdfDictionaryProperties.parent) { + annotationData.addAll(utf8.encode(' $_annotationID 0 R')); + annotationData.addAll(utf8.encode('/Page $pageNumber')); + } else if (key.name == PdfDictionaryProperties.irt) { + if (primitive.object != null && primitive.object is PdfDictionary) { + final IPdfPrimitive? inReplyTo = primitive.object; + if (inReplyTo != null && + inReplyTo is PdfDictionary && + inReplyTo.containsKey('NM')) { + final IPdfPrimitive? name = + PdfCrossTable.dereference(inReplyTo['NM']); + if (name != null && name is PdfString) { + if (name.value != null) { + annotationData.addAll(utf8 + .encode('(${_getFormattedStringFDF(name.value!)})')); + } + } + } + } + } else if (key.name != PdfDictionaryProperties.p) { + currentID++; + annotationData.addAll(utf8.encode(' $currentID 0 R')); + if (isStream) { + streamReferences.add(currentID); + } + if (primitive.object != null) { + dictionaries[currentID] = primitive.object!; + } + } + } + } + isStream = false; + } + return { + 'exportData': annotationData, + 'currentID': currentID + }; + } + + String _getFormattedStringFDF(String value) { + String result = ''; + for (int i = 0; i < value.length; i++) { + final int c = value.codeUnitAt(i); + if (c == 40 || c == 41) { + result += r'\'; + } + if (c == 13 || c == 10) { + if (c == 13) { + result += r'\r'; + } + if (c == 10) { + result += r'\n'; + } + continue; + } + result += String.fromCharCode(c); + } + return result; + } + + Map _appendArrayElements( + PdfArray array, + int currentID, + bool isStream, + Map dictionaries, + List streamReferences) { + final List arrayData = []; + arrayData.addAll(utf8.encode('[')); + if (array.elements.isNotEmpty) { + final int count = array.elements.length; + for (int i = 0; i < count; i++) { + final IPdfPrimitive? element = array.elements[i]; + if (i != 0 && + element != null && + (element is PdfNumber || + element is PdfReferenceHolder || + element is PdfBoolean)) { + arrayData.addAll(utf8.encode(' ')); + } + final Map result = _appendElement( + element!, currentID, isStream, dictionaries, streamReferences); + arrayData.addAll(result['exportData'] as List); + currentID = result['currentID'] as int; + } + } + arrayData.addAll(utf8.encode(']')); + return {'exportData': arrayData, 'currentID': currentID}; + } + + Map _appendElement( + IPdfPrimitive element, + int currentID, + bool isStream, + Map dictionaries, + List streamReferences) { + final List exportData = []; + if (element is PdfNumber) { + exportData.addAll(utf8.encode(element.value!.toString())); + } else if (element is PdfName) { + exportData.addAll(utf8.encode(element.toString())); + } else if (element is PdfString) { + if (element.value != null) { + exportData + .addAll(utf8.encode('(${_getFormattedStringFDF(element.value!)})')); + } + } else if (element is PdfBoolean) { + exportData.addAll(utf8.encode(element.value!.toString())); + } else if (element is PdfReferenceHolder) { + currentID++; + if (isStream) { + streamReferences.add(currentID); + } + if (element.object != null) { + dictionaries[currentID] = element.object!; + } + exportData.addAll(utf8.encode('$currentID 0 R')); + } else if (element is PdfArray) { + final Map result = _appendArrayElements( + element, currentID, isStream, dictionaries, streamReferences); + currentID = result['currentID'] as int; + exportData.addAll(result['exportData'] as List); + } else if (element is PdfDictionary) { + exportData.addAll(utf8.encode('<<')); + final Map data = _getEntriesInDictionary( + dictionaries, streamReferences, currentID, element, isStream); + exportData.addAll(data['exportData'] as List); + currentID = data['currentID'] as int; + exportData.addAll(utf8.encode('>>')); + } + return {'exportData': exportData, 'currentID': currentID}; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_parser.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_parser.dart new file mode 100644 index 000000000..f70a8ac73 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_parser.dart @@ -0,0 +1,392 @@ +import 'dart:convert'; + +import '../../interfaces/pdf_interface.dart'; +import '../io/enums.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../io/pdf_parser.dart'; +import '../io/pdf_reader.dart'; +import '../pages/pdf_page.dart'; +import '../pdf_document/pdf_document.dart'; +import '../primitives/pdf_array.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; +import 'json_parser.dart'; + +/// The class provides methods and properties to handle the loaded annotations from the existing PDF document for Fdf export and import. +class FdfParser { + //Constructor. + /// Initializes a new instance of the [FdfParser] class with the specified data. + FdfParser(List data) { + _data = data; + _reader = PdfReader(data); + _fdfObjects = {}; + } + + //Fields + late List _data; + PdfReader? _reader; + late Map _fdfObjects; + PdfParser? _parser; + Map? _annotationObjects; + Map? _groupObjects; + + //Properties + /// Gets the grouped objects. + Map get groupedObjects { + _groupObjects ??= {}; + return _groupObjects!; + } + +//Implementations. + /// Parse the annotation data. + void parseAnnotationData() { + final PdfCrossTable table = PdfCrossTable.fromFdf(_data); + if (table.crossTable != null) { + _parser = table.crossTable!.parser; + final int startPos = _checkFdf(); + if (startPos != -1) { + final int? endPos = _reader!.seekEnd(); + _parser!.setOffset(startPos); + _parser!.advance(); + while (_parser!.next != null && _parser!.next! == PdfTokenType.number) { + _parseObject(_fdfObjects); + } + if (_parser!.lexer != null && + _parser!.lexer!.text == PdfOperators.trailer) { + final IPdfPrimitive trailer = _parser!.trailer(); + _fdfObjects[PdfOperators.trailer] = trailer; + } + _parser!.advance(); + while (_parser!.lexer != null && + _parser!.lexer!.position != endPos && + _parser!.next == PdfTokenType.number) { + _parseObject(_fdfObjects); + } + } + } + } + + /// Imports the annotation data from FDF stream + void importAnnotations(PdfDocument document) { + _annotationObjects = _getAnnotationObjects(); + final bool hasAnnotations = _groupAnnotations(); + if (hasAnnotations && _annotationObjects != null) { + for (final PdfReferenceHolder holder in _annotationObjects!.values) { + final IPdfPrimitive? dictionary = holder.object; + if (dictionary != null && + dictionary is PdfDictionary && + dictionary.items != null && + dictionary.items!.isNotEmpty) { + _parseDictionary(dictionary); + if (dictionary.containsKey(PdfDictionaryProperties.irt)) { + final IPdfPrimitive? inReplyTo = + dictionary[PdfDictionaryProperties.irt]; + if (inReplyTo != null && + inReplyTo is PdfString && + !isNullOrEmpty(inReplyTo.value)) { + if (groupedObjects.containsKey(inReplyTo.value)) { + final String referenceString = groupedObjects[inReplyTo.value]!; + dictionary[PdfDictionaryProperties.irt] = + _annotationObjects![referenceString]; + } + } + } + if (dictionary.containsKey(PdfDictionaryProperties.contents)) { + final IPdfPrimitive? content = + dictionary[PdfDictionaryProperties.contents]; + if (content != null && + content is PdfString && + !isNullOrEmpty(content.value)) { + String contentText = content.value!; + if (RegExp(r'[\u0085-\u00FF]').hasMatch(contentText)) { + final List bytes = content.pdfEncode(document); + contentText = utf8.decode(bytes); + if (contentText.startsWith('(') && contentText.endsWith(')')) { + while (contentText.startsWith('(')) { + contentText = contentText.substring(1); + } + while (contentText.endsWith(')')) { + contentText = + contentText.substring(0, contentText.length - 1); + } + } + dictionary[PdfDictionaryProperties.contents] = + PdfString(contentText); + if (dictionary.containsKey('RC')) { + dictionary.setString('RC', + '

$contentText

'); + } + dictionary.modify(); + } + } + } + if (dictionary.containsKey('Page')) { + final IPdfPrimitive? pageNumber = dictionary['Page']; + if (pageNumber != null && pageNumber is PdfNumber) { + final int pageIndex = pageNumber.value!.toInt(); + if (pageIndex < document.pages.count) { + final PdfPage loadedPage = document.pages[pageIndex]; + PdfPageHelper.getHelper(loadedPage).importAnnotation = true; + final PdfDictionary? pageDictionary = + PdfPageHelper.getHelper(loadedPage).dictionary; + if (pageDictionary != null) { + if (!pageDictionary + .containsKey(PdfDictionaryProperties.annots)) { + pageDictionary[PdfDictionaryProperties.annots] = PdfArray(); + } + final IPdfPrimitive? annots = PdfCrossTable.dereference( + pageDictionary[PdfDictionaryProperties.annots]); + if (annots != null && annots is PdfArray) { + annots.elements.add(holder); + annots.changed = true; + pageDictionary.modify(); + } + } + } + dictionary.remove('Page'); + } + } + } + } + } + } + + /// internal method + void dispose() { + _fdfObjects.clear(); + _reader = null; + _reader = null; + _parser = null; + if (_annotationObjects != null) { + _annotationObjects!.clear(); + } + _annotationObjects = null; + } + + Map _getAnnotationObjects() { + final Map mappedObjects = + {}; + final Map objects = _fdfObjects; + if (objects.isNotEmpty && objects.containsKey(PdfOperators.trailer)) { + final IPdfPrimitive trailer = objects[PdfOperators.trailer]!; + if (trailer is PdfDictionary && + trailer.containsKey(PdfDictionaryProperties.root)) { + final IPdfPrimitive? holder = trailer[PdfDictionaryProperties.root]; + if (holder != null && holder is PdfReferenceHolder) { + PdfReference? reference = holder.reference; + if (reference != null) { + final String rootKey = '${reference.objNum} ${reference.genNum}'; + if (objects.containsKey(rootKey)) { + final IPdfPrimitive root = objects[rootKey]!; + if (root is PdfDictionary && root.containsKey('FDF')) { + final IPdfPrimitive? fdf = root['FDF']; + if (fdf != null && + fdf is PdfDictionary && + fdf.containsKey(PdfDictionaryProperties.annots)) { + final IPdfPrimitive? annots = + fdf[PdfDictionaryProperties.annots]; + if (annots != null && + annots is PdfArray && + annots.count != 0) { + for (final IPdfPrimitive? holder in annots.elements) { + if (holder != null && holder is PdfReferenceHolder) { + reference = holder.reference; + if (reference != null) { + final String key = + '${reference.objNum} ${reference.genNum}'; + if (objects.containsKey(key)) { + mappedObjects[key] = + PdfReferenceHolder(objects[key]); + objects.remove(key); + } + } + } + } + } + } + objects.remove(rootKey); + } + } + } + } + } + objects.remove(PdfOperators.trailer); + } + return mappedObjects; + } + + bool _groupAnnotations() { + if (_annotationObjects != null && _annotationObjects!.isNotEmpty) { + _annotationObjects!.forEach((String key, PdfReferenceHolder value) { + final IPdfPrimitive? dictionary = value.object; + if (dictionary != null && + dictionary is PdfDictionary && + dictionary.containsKey('NM')) { + final IPdfPrimitive? name = dictionary['NM']; + if (name != null && name is PdfString && !isNullOrEmpty(name.value)) { + groupedObjects[name.value!] = key; + } + } + }); + return true; + } + return false; + } + + int _checkFdf() { + const int headerLength = 8; + final String header = utf8.decode(_data, allowMalformed: true); + final int index = header.indexOf('%FDF-'); + if (index < 0) { + throw ArgumentError( + 'The source is not a valid FDF file because it does not start with"%FDF-"'); + } + return index + headerLength; + } + + void _parseObject(Map objects) { + final FdfObject? obj = _parser!.parseObject(); + if (obj != null && obj.objectNumber > 0 && obj.generationNumber >= 0) { + final String key = '${obj.objectNumber} ${obj.generationNumber}'; + objects[key] = obj.object; + } + _parser!.advance(); + } + + void _parseDictionary(PdfDictionary dictionary, [PdfName? key]) { + if (key != null) { + final IPdfPrimitive? primitive = dictionary[key]; + if (primitive != null) { + if (primitive is PdfDictionary) { + _parseDictionary(primitive); + } else if (primitive is PdfArray) { + _parseArray(primitive); + } else if (primitive is PdfReferenceHolder) { + final PdfReference? reference = primitive.reference; + if (reference != null) { + final String objectKey = '${reference.objNum} ${reference.genNum}'; + if (_annotationObjects != null && + _annotationObjects!.containsKey(objectKey)) { + dictionary[key] = _annotationObjects![objectKey]; + dictionary.modify(); + } else if (_fdfObjects.containsKey(objectKey)) { + final Map objects = _fdfObjects; + if (objects[objectKey] is PdfReferenceHolder) { + dictionary[key] = objects[objectKey]; + dictionary.modify(); + } else if (objects[objectKey] is PdfName) { + final PdfName obj = objects[objectKey]! as PdfName; + final PdfReferenceHolder holder = PdfReferenceHolder(obj); + dictionary[key] = holder; + objects[objectKey] = holder; + dictionary.modify(); + } else if (objects[objectKey] is PdfArray) { + final PdfArray obj = objects[objectKey]! as PdfArray; + _parseArray(obj); + final PdfReferenceHolder holder = PdfReferenceHolder(obj); + dictionary[key] = holder; + objects[objectKey] = holder; + dictionary.modify(); + } else if (objects[objectKey] is PdfStream) { + final PdfStream obj = objects[objectKey]! as PdfStream; + _parseDictionary(obj); + final PdfReferenceHolder holder = PdfReferenceHolder(obj); + dictionary[key] = holder; + objects[objectKey] = holder; + dictionary.modify(); + } else if (objects[objectKey] is PdfDictionary) { + final PdfDictionary obj = objects[objectKey]! as PdfDictionary; + _parseDictionary(obj); + final PdfReferenceHolder holder = PdfReferenceHolder(obj); + dictionary[key] = holder; + objects[objectKey] = holder; + dictionary.modify(); + } + } else { + dictionary.remove(key); + } + } + } + } + } else { + final List names = _getKeys(dictionary); + for (int i = 0; i < names.length; i++) { + _parseDictionary(dictionary, names[i]); + } + } + } + + List _getKeys(PdfDictionary dictionary) { + final List names = []; + for (final PdfName? name in dictionary.items!.keys) { + if (name != null) { + names.add(name); + } + } + return names; + } + + void _parseArray(PdfArray array) { + final int count = array.elements.length; + for (int i = 0; i < count; i++) { + final IPdfPrimitive? element = array[i]; + if (element != null && element is PdfReferenceHolder) { + final PdfReference? reference = element.reference; + if (reference != null) { + final String objectKey = '${reference.objNum} ${reference.genNum}'; + if (_annotationObjects != null && + _annotationObjects!.containsKey(objectKey)) { + array.elements[i] = _annotationObjects![objectKey]; + array.changed = true; + } else if (_fdfObjects.containsKey(objectKey)) { + final Map objects = _fdfObjects; + if (objects[objectKey] is PdfReferenceHolder) { + array.elements[i] = objects[objectKey]; + array.changed = true; + } else if (objects[objectKey] != null && + objects[objectKey] is PdfDictionary) { + _parseDictionary(objects[objectKey]! as PdfDictionary); + final PdfReferenceHolder holder = + PdfReferenceHolder(objects[objectKey]); + array.elements[i] = holder; + objects[objectKey] = holder; + array.changed = true; + } + } + } + } + } + } +} + +/// The class provides fields and properties to handle objects in Fdf stream. +class FdfObject { + // Constructor + /// Initializes a new instance of the [FdfObject] class with the specified object number, generation number and object. + FdfObject(PdfNumber objNum, PdfNumber genNum, IPdfPrimitive obj) { + _objNumber = objNum.value!.toInt(); + _genNumber = genNum.value!.toInt(); + _object = obj; + } + + // Fields + int? _objNumber; + int? _genNumber; + IPdfPrimitive? _object; + + // Properties + /// Gets the object number. + int get objectNumber => _objNumber!; + + /// Gets the generation number. + int get generationNumber => _genNumber!; + + /// Gets the object. + IPdfPrimitive get object => _object!; +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_document.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_document.dart new file mode 100644 index 000000000..871ad62e8 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_document.dart @@ -0,0 +1,1132 @@ +import 'dart:convert'; + +import 'package:intl/intl.dart'; + +import '../../interfaces/pdf_interface.dart'; +import '../forms/pdf_xfdf_document.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../pdf_document/pdf_document.dart'; +import '../primitives/pdf_array.dart'; +import '../primitives/pdf_boolean.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; +import 'enum.dart'; +import 'json_parser.dart'; +import 'pdf_annotation.dart'; + +/// Represents a class which contains the methods and properties to export annotations in JSON format. +class JsonDocument { + //Consturctor. + /// Initializes a new instance of the [JsonDocument] class. + JsonDocument(this._document); + + //Fields + late final PdfDocument _document; + // late String _fileName; + bool _skipBorderStyle = false; + + //Implementation + /// Internal method. + void exportAnnotationData(Map table, bool exportAppearance, + int pageIndex, PdfDictionary dictionary) { + bool hasAppearance = exportAppearance; + _skipBorderStyle = false; + final String? annotationType = _getAnnotationType(dictionary); + if (!isNullOrEmpty(annotationType)) { + table['type'] = annotationType!; + table['page'] = pageIndex.toString(); + switch (annotationType) { + case PdfDictionaryProperties.line: + if (dictionary.containsKey(PdfDictionaryProperties.l)) { + final IPdfPrimitive? linePoints = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.l]); + if (linePoints != null && + linePoints is PdfArray && + linePoints.count == 4 && + linePoints[0] != null && + linePoints[0] is PdfNumber && + linePoints[1] != null && + linePoints[1] is PdfNumber && + linePoints[2] != null && + linePoints[2] is PdfNumber && + linePoints[3] != null && + linePoints[3] is PdfNumber) { + table['start'] = + '${(linePoints[0]! as PdfNumber).value},${(linePoints[1]! as PdfNumber).value}'; + table['end'] = + '${(linePoints[2]! as PdfNumber).value},${(linePoints[3]! as PdfNumber).value}'; + } + } + break; + case 'Stamp': + if (!hasAppearance) { + hasAppearance = true; + } + break; + case 'Square': + if (!hasAppearance) { + hasAppearance = true; + } + break; + } + if (dictionary.containsKey(PdfDictionaryProperties.be) && + dictionary.containsKey(PdfDictionaryProperties.bs)) { + final IPdfPrimitive? borderEffect = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.be]); + if (borderEffect != null && + borderEffect is PdfDictionary && + borderEffect.containsKey(PdfDictionaryProperties.s)) { + _skipBorderStyle = true; + } + } + _writeDictionary(pageIndex, dictionary, hasAppearance, table); + } + } + + /// Internal method. + String convertToJson(Map value) { + int j = 0; + String json = '{'; + value.forEach((String fieldKey, String fieldValue) { + if (fieldValue.startsWith('{') || fieldValue.startsWith('[')) { + json = '$json"${_replaceJsonDelimiters(fieldKey)}":$fieldValue'; + } else { + if (fieldValue.startsWith(' ') && + fieldValue.length > 1 && + (fieldValue[1] == '[' || fieldValue[1] == '{')) { + fieldValue = fieldValue.trim(); + } + json = '$json"${_replaceJsonDelimiters(fieldKey)}":"$fieldValue"'; + } + if (j < value.length - 1) { + json = '$json,'; + } + j++; + }); + json = '$json}'; + return json; + } + + void _writeDictionary(int pageIndex, PdfDictionary dictionary, + bool hasAppearance, Map table) { + bool isBSdictionary = false; + if (dictionary.containsKey(PdfDictionaryProperties.type)) { + final IPdfPrimitive? name = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.type]); + if (name != null && + name is PdfName && + name.name != null && + name.name! == PdfDictionaryProperties.border && + _skipBorderStyle) { + isBSdictionary = true; + } + } + dictionary.items!.forEach((PdfName? name, IPdfPrimitive? value) { + final String? key = name!.name; + if (key != null && + !(key == PdfDictionaryProperties.p || + key == PdfDictionaryProperties.parent)) { + final IPdfPrimitive? primitive = value; + if (primitive != null) { + if (primitive is PdfReferenceHolder) { + final IPdfPrimitive? obj = primitive.object; + if (obj != null && obj is PdfDictionary) { + switch (key) { + case PdfDictionaryProperties.bs: + _writeDictionary(pageIndex, obj, false, table); + break; + case PdfDictionaryProperties.be: + _writeDictionary(pageIndex, obj, false, table); + break; + case PdfDictionaryProperties.irt: + if (obj.containsKey('NM')) { + final String? value = _getValue(obj['NM']); + if (value != null) { + table['inreplyto'] = value; + } + } + break; + } + } + } else if (primitive is PdfDictionary) { + _writeDictionary(pageIndex, primitive, false, table); + } else if (value != null && + (!isBSdictionary || + (isBSdictionary && key != PdfDictionaryProperties.s))) { + _writeAttribute(key, value, pageIndex, dictionary, table); + } + } + } + }); + if (dictionary.containsKey(PdfDictionaryProperties.measure)) { + _exportMeasureDictionary(dictionary, table); + } + if (hasAppearance && dictionary.containsKey(PdfDictionaryProperties.ap)) { + List? bytes = + _getAppearanceString(dictionary[PdfDictionaryProperties.ap]!); + if (bytes.isNotEmpty) { + table['appearance'] = base64.encode(bytes); + } + bytes = null; + } + if (dictionary.containsKey('Sound')) { + final IPdfPrimitive? sound = + PdfCrossTable.dereference(dictionary['Sound']); + if (sound != null && sound is PdfStream) { + if (sound.containsKey('B')) { + final String? bits = _getValue(sound['B']); + if (!isNullOrEmpty(bits)) { + table['bits'] = bits!; + } + } + if (sound.containsKey(PdfDictionaryProperties.c)) { + final String? channels = _getValue(sound[PdfDictionaryProperties.c]); + if (!isNullOrEmpty(channels)) { + table['channels'] = channels!; + } + } + if (sound.containsKey(PdfDictionaryProperties.e)) { + final String? encoding = _getValue(sound[PdfDictionaryProperties.e]); + if (!isNullOrEmpty(encoding)) { + table['encoding'] = encoding!; + } + } + if (sound.containsKey(PdfDictionaryProperties.r)) { + final String? rate = _getValue(sound[PdfDictionaryProperties.r]); + if (!isNullOrEmpty(rate)) { + table['rate'] = rate!; + } + } + if (sound.dataStream != null && sound.dataStream!.isNotEmpty) { + final String data = PdfString.bytesToHex(sound.dataStream!); + if (!isNullOrEmpty(data)) { + table[XfdfProperties.mode.toLowerCase()] = 'raw'; + table['encodings'] = 'hex'; + if (sound.containsKey(PdfDictionaryProperties.length)) { + final String? length = + _getValue(sound[PdfDictionaryProperties.length]); + if (!isNullOrEmpty(length)) { + table[PdfDictionaryProperties.length.toLowerCase()] = length!; + } + } + if (sound.containsKey(PdfDictionaryProperties.filter)) { + final String? filter = + _getValue(sound[PdfDictionaryProperties.filter]); + if (!isNullOrEmpty(filter)) { + table[PdfDictionaryProperties.filter.toLowerCase()] = filter!; + } + } + table['data'] = data; + } + } + } + } else if (dictionary.containsKey(PdfDictionaryProperties.fs)) { + final IPdfPrimitive? fsDictionary = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.fs]); + if (fsDictionary != null && fsDictionary is PdfDictionary) { + if (fsDictionary.containsKey(PdfDictionaryProperties.f)) { + final String? file = + _getValue(fsDictionary[PdfDictionaryProperties.f]); + if (!isNullOrEmpty(file)) { + table['file'] = file!; + } + } + if (fsDictionary.containsKey(PdfDictionaryProperties.ef)) { + final IPdfPrimitive? efDictionary = PdfCrossTable.dereference( + fsDictionary[PdfDictionaryProperties.ef]); + if (efDictionary != null && + efDictionary is PdfDictionary && + efDictionary.containsKey(PdfDictionaryProperties.f)) { + final IPdfPrimitive? fStream = PdfCrossTable.dereference( + efDictionary[PdfDictionaryProperties.f]); + if (fStream != null && fStream is PdfStream) { + if (fStream.containsKey(PdfDictionaryProperties.params)) { + final IPdfPrimitive? paramsDictionary = + PdfCrossTable.dereference( + fStream[PdfDictionaryProperties.params]); + if (paramsDictionary != null && + paramsDictionary is PdfDictionary) { + if (paramsDictionary + .containsKey(PdfDictionaryProperties.creationDate)) { + final IPdfPrimitive? creationDate = + PdfCrossTable.dereference(paramsDictionary[ + PdfDictionaryProperties.creationDate]); + if (creationDate != null && creationDate is PdfString) { + final DateTime dateTime = + dictionary.getDateTime(creationDate); + table['creation'] = + DateFormat('M/d/yyyy h:mm:ss a').format(dateTime); + } + } + if (paramsDictionary + .containsKey(PdfDictionaryProperties.modificationDate)) { + final IPdfPrimitive? modifyDate = PdfCrossTable.dereference( + paramsDictionary[ + PdfDictionaryProperties.modificationDate]); + if (modifyDate != null && modifyDate is PdfString) { + final DateTime dateTime = + dictionary.getDateTime(modifyDate); + table['modification'] = + DateFormat('M/d/yyyy h:mm:ss a').format(dateTime); + } + } + if (paramsDictionary + .containsKey(PdfDictionaryProperties.size)) { + final String? size = _getValue( + paramsDictionary[PdfDictionaryProperties.size]); + if (!isNullOrEmpty(size)) { + table[PdfDictionaryProperties.size.toLowerCase()] = size!; + } + } + if (paramsDictionary.containsKey('CheckSum')) { + final String? checksumValue = + _getValue(paramsDictionary['CheckSum']); + if (!isNullOrEmpty(checksumValue)) { + final List checksum = utf8.encode(checksumValue!); + final String hexString = PdfString.bytesToHex(checksum); + table['checksum'] = hexString; + } + } + } + } + if (fStream.dataStream != null && + fStream.dataStream!.isNotEmpty) { + final String data = PdfString.bytesToHex(fStream.dataStream!); + if (!isNullOrEmpty(data)) { + table[XfdfProperties.mode.toLowerCase()] = + XfdfProperties.raw.toLowerCase(); + table[PdfDictionaryProperties.encoding.toLowerCase()] = + XfdfProperties.hex.toLowerCase(); + if (fStream.containsKey(PdfDictionaryProperties.length)) { + final String? length = + _getValue(fStream[PdfDictionaryProperties.length]); + if (!isNullOrEmpty(length)) { + table[PdfDictionaryProperties.length.toLowerCase()] = + length!; + } + } + if (fStream.containsKey(PdfDictionaryProperties.filter)) { + final String? filter = + _getValue(fStream[PdfDictionaryProperties.filter]); + if (!isNullOrEmpty(filter)) { + table[PdfDictionaryProperties.filter.toLowerCase()] = + filter!; + } + } + table[XfdfProperties.data.toLowerCase()] = data; + } + } + } + } + } + } + } + } + + List _getAppearanceString(IPdfPrimitive primitive) { + final Map appearanceTable = {}; + final Map parentTable = {}; + final IPdfPrimitive? appearance = PdfCrossTable.dereference(primitive); + if (appearance != null && appearance is PdfDictionary) { + _writeAppearanceDictionary(appearanceTable, appearance); + } + parentTable['ap'] = convertToJson(appearanceTable); + final String jsonData = convertToJson(parentTable); + return utf8.encode(jsonData); + } + + void _writeAppearanceDictionary( + Map textWriter, PdfDictionary dictionary) { + if (dictionary.count > 0) { + dictionary.items!.forEach((PdfName? name, IPdfPrimitive? value) { + _writeObject(textWriter, name!.name, value, null); + }); + } + } + + void _writeObject(Map? textWriter, String? key, + IPdfPrimitive? primitive, List>? arrayWriter) { + if (primitive != null) { + final String type = primitive.runtimeType.toString(); + switch (type) { + case 'PdfReferenceHolder': + final PdfReferenceHolder holder = primitive as PdfReferenceHolder; + if (holder.object != null) { + _writeObject(textWriter, key, holder.object, arrayWriter); + } + break; + case 'PdfDictionary': + final PdfDictionary dictionaryElement = primitive as PdfDictionary; + final Map mainTable = {}; + final Map subtable = {}; + _writeAppearanceDictionary(subtable, dictionaryElement); + mainTable['dict'] = convertToJson(subtable); + if (key != null) { + textWriter![key] = convertToJson(mainTable); + } else { + arrayWriter!.add(mainTable); + } + break; + case 'PdfStream': + final IPdfPrimitive? streamElement = (primitive as PdfStream) + .cloneObject(PdfDocumentHelper.getHelper(_document).crossTable); + if (streamElement != null && streamElement is PdfStream) { + if (streamElement.dataStream != null && + streamElement.dataStream!.isNotEmpty) { + final Map streamTable = {}; + _writeAppearanceDictionary(streamTable, streamElement); + final Map dataTable = {}; + final String? type = + _getValue(streamElement[PdfDictionaryProperties.subtype]); + if ((streamElement.containsKey(PdfDictionaryProperties.subtype) && + !isNullOrEmpty(type) && + PdfDictionaryProperties.image == type!) || + (!streamElement.containsKey(PdfDictionaryProperties.type) && + !streamElement + .containsKey(PdfDictionaryProperties.subtype))) { + dataTable['mode'] = 'raw'; + dataTable['encoding'] = 'hex'; + final String data = + PdfString.bytesToHex(streamElement.dataStream!); + if (!isNullOrEmpty(data)) { + dataTable['bytes'] = data; + } + } else if (streamElement + .containsKey(PdfDictionaryProperties.subtype) && + !isNullOrEmpty(type) && + (PdfDictionaryProperties.form == type || + 'CIDFontType0C' == type || + 'OpenType' == type)) { + dataTable['mode'] = 'raw'; + dataTable['encoding'] = 'hex'; + streamElement.decompress(); + final String data = + PdfString.bytesToHex(streamElement.dataStream!); + if (!isNullOrEmpty(data)) { + dataTable['bytes'] = data; + } + } else { + dataTable['mode'] = 'filtered'; + dataTable['encoding'] = 'ascii'; + streamElement.decompress(); + final String ascii = + PdfString.bytesToHex(streamElement.dataStream!); + if (!isNullOrEmpty(ascii)) { + dataTable['bytes'] = ascii; + } + } + streamTable['data'] = convertToJson(dataTable); + final Map keyValuePairs = {}; + keyValuePairs['stream'] = convertToJson(streamTable); + if (key != null) { + textWriter![key] = convertToJson(keyValuePairs); + } else { + arrayWriter!.add(keyValuePairs); + } + } + } + break; + case 'PdfBoolean': + final PdfBoolean booleanElement = primitive as PdfBoolean; + final Map boolean = {}; + boolean['boolean'] = booleanElement.value.toString(); + if (key != null) { + textWriter![key] = convertToJson(boolean); + } else { + arrayWriter!.add(boolean); + } + break; + case 'PdfName': + if (primitive is PdfName && primitive.name != null) { + final Map name = {}; + name['name'] = primitive.name!; + if (key != null) { + textWriter![key] = convertToJson(name); + } else { + arrayWriter!.add(name); + } + } + break; + case 'PdfString': + if (primitive is PdfString && primitive.value != null) { + final Map stringValue = {}; + stringValue['string'] = primitive.value!; + if (key != null) { + textWriter![key] = convertToJson(stringValue); + } else { + arrayWriter!.add(stringValue); + } + } + break; + case 'PdfNumber': + if (primitive is PdfNumber && primitive.value != null) { + if (primitive.value is int) { + final Map integer = {}; + integer['int'] = primitive.value!.toString(); + if (key != null) { + textWriter![key] = convertToJson(integer); + } else { + arrayWriter!.add(integer); + } + } else { + final String value = + primitive.value!.toDouble().toStringAsFixed(6); + final Map integer = {}; + integer['fixed'] = value; + if (key != null) { + textWriter![key] = convertToJson(integer); + } else { + arrayWriter!.add(integer); + } + } + } + break; + case 'PdfNull': + final Map nullValue = {}; + nullValue['null'] = 'null'; + if (key != null) { + textWriter![key] = convertToJson(nullValue); + } else { + arrayWriter!.add(nullValue); + } + break; + case 'PdfArray': + final List> arrayDict = >[]; + _writeArray(arrayDict, primitive as PdfArray); + final Map tempDict = {}; + tempDict['array'] = _convertListToJson(arrayDict); + if (key != null) { + textWriter![key] = convertToJson(tempDict); + } else { + arrayWriter!.add(tempDict); + } + break; + } + } + } + + void _writeArray(List> textWriter, PdfArray array) { + for (final IPdfPrimitive? element in array.elements) { + if (element != null) { + _writeObject(null, null, element, textWriter); + } + } + } + + String _convertListToJson(List> value) { + String json = '['; + for (int i = 0; i < value.length; i++) { + json += convertToJson(value[i]); + if (i < value.length - 1) { + json = '$json,'; + } + } + json += ']'; + return json; + } + + String? _getValue(IPdfPrimitive? primitive) { + String? value; + if (primitive != null) { + if (primitive is PdfName) { + value = primitive.name; + } else if (primitive is PdfBoolean) { + value = primitive.value.toString(); + } else if (primitive is PdfString) { + value = primitive.value; + if (value != null && (value.startsWith('[') || value.startsWith('{'))) { + value = ' $value'; + } + value = _getValidString(value); + } else if (primitive is PdfArray) { + if (primitive.elements.isNotEmpty) { + for (int i = 0; i < primitive.elements.length; i++) { + final String? colorValue = _getValue(primitive.elements[i]); + if (colorValue != null) { + value = i == 0 ? colorValue : '$value,$colorValue'; + } + } + } + } else if (primitive is PdfNumber) { + if (primitive.value != null) { + value = primitive.value!.toString(); + } + } + } + return value; + } + + String? _getValidString(String? value) { + if (value != null) { + if (value.contains('"')) { + final RegExp regExp = RegExp(r'(? table) { + switch (key) { + case PdfDictionaryProperties.c: + final String color = _getColor(primitive); + if (!isNullOrEmpty(color)) { + table['color'] = color; + } + break; + case PdfDictionaryProperties.da: + final IPdfPrimitive? defaultAppearance = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.da]); + if (defaultAppearance != null && + defaultAppearance is PdfString && + !isNullOrEmpty(defaultAppearance.value)) { + table['defaultappearance'] = defaultAppearance.value!; + } + break; + case PdfDictionaryProperties.ic: + final String interiorColor = _getColor(primitive); + if (!isNullOrEmpty(interiorColor)) { + table['interior-color'] = interiorColor; + } + break; + case PdfDictionaryProperties.m: + final IPdfPrimitive? modifiedDate = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.m]); + if (modifiedDate != null && + modifiedDate is PdfString && + !isNullOrEmpty(modifiedDate.value)) { + final DateTime dateTime = dictionary.getDateTime(modifiedDate); + table['date'] = DateFormat('M/d/yyyy h:mm:ss a').format(dateTime); + } + break; + case 'NM': + final String? value = _getValue(primitive); + if (!isNullOrEmpty(value)) { + table[PdfDictionaryProperties.name.toLowerCase()] = value!; + } + break; + case PdfDictionaryProperties.name: + final String? value = _getValue(primitive); + if (!isNullOrEmpty(value)) { + table['icon'] = value!; + } + break; + case PdfDictionaryProperties.subj: + final String? value = _getValue(primitive); + if (!isNullOrEmpty(value)) { + table[PdfDictionaryProperties.subject.toLowerCase()] = value!; + } + break; + case PdfDictionaryProperties.t: + final String? value = _getValue(primitive); + if (!table.containsKey(PdfDictionaryProperties.title.toLowerCase())) { + table[PdfDictionaryProperties.title.toLowerCase()] = value!; + } + break; + case PdfDictionaryProperties.rect: + final String? rect = _getValue(primitive); + if (!isNullOrEmpty(rect)) { + final List styleArray = rect!.split(','); + final Map subTable = {}; + subTable['x'] = styleArray[0]; + subTable['y'] = styleArray[1]; + subTable['width'] = styleArray[2]; + subTable['height'] = styleArray[3]; + table[key.toLowerCase()] = convertToJson(subTable); + } + break; + case PdfDictionaryProperties.creationDate: + final IPdfPrimitive? createDate = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.creationDate]); + if (createDate != null && + createDate is PdfString && + !isNullOrEmpty(createDate.value)) { + final DateTime creationDate = dictionary.getDateTime(createDate); + table[key.toLowerCase()] = + DateFormat('M/d/yyyy h:mm:ss a').format(creationDate); + } + break; + case PdfDictionaryProperties.rotate: + final String? rotation = _getValue(primitive); + if (!isNullOrEmpty(rotation)) { + table['rotation'] = rotation!; + } + break; + case PdfDictionaryProperties.w: + final String? width = _getValue(primitive); + if (!isNullOrEmpty(width)) { + table[PdfDictionaryProperties.width.toLowerCase()] = width!; + } + break; + case PdfDictionaryProperties.le: + if (primitive is PdfArray) { + if (primitive.count == 2) { + table['head'] = _getValue(primitive.elements[0])!; + table['tail'] = _getValue(primitive.elements[1])!; + } + } else if (primitive is PdfName) { + final String? head = _getValue(primitive); + if (!isNullOrEmpty(head)) { + table['head'] = head!; + } + } + break; + case 'S': + final String? style = _getValue(primitive); + if (!isNullOrEmpty(style)) { + switch (style) { + case PdfDictionaryProperties.d: + table['style'] = 'dash'; + break; + case PdfDictionaryProperties.c: + table['style'] = 'cloudy'; + break; + case PdfDictionaryProperties.s: + table['style'] = 'solid'; + break; + case 'B': + table['style'] = 'bevelled'; + break; + case PdfDictionaryProperties.i: + table['style'] = 'inset'; + break; + case PdfDictionaryProperties.u: + table['style'] = 'underline'; + break; + } + } + break; + case PdfDictionaryProperties.d: + if (!table.containsKey('dashes')) { + final String? dashes = _getValue(primitive); + if (!isNullOrEmpty(dashes)) { + table['dashes'] = dashes!; + } + } + break; + case PdfDictionaryProperties.i: + final String? intensity = _getValue(primitive); + if (!isNullOrEmpty(intensity)) { + table['intensity'] = intensity!; + } + break; + case PdfDictionaryProperties.rd: + final String? fringe = _getValue(primitive); + if (!isNullOrEmpty(fringe)) { + table['fringe'] = fringe!; + } + break; + case PdfDictionaryProperties.it: + final String? it = _getValue(primitive); + if (!isNullOrEmpty(it)) { + table[key] = it!; + } + break; + case 'RT': + final String? replyType = _getValue(primitive); + if (!isNullOrEmpty(replyType)) { + table['replyType'] = replyType!.toLowerCase(); + } + break; + case PdfDictionaryProperties.ll: + final String? leaderLength = _getValue(primitive); + if (!isNullOrEmpty(leaderLength)) { + table['leaderLength'] = leaderLength!; + } + break; + case PdfDictionaryProperties.lle: + final String? leaderExtend = _getValue(primitive); + if (!isNullOrEmpty(leaderExtend)) { + table['leaderExtend'] = leaderExtend!; + } + break; + case PdfDictionaryProperties.cap: + final String? caption = _getValue(primitive); + if (!isNullOrEmpty(caption)) { + table['caption'] = caption!; + } + break; + case PdfDictionaryProperties.cp: + final String? captionStyle = _getValue(primitive); + if (!isNullOrEmpty(captionStyle)) { + table['caption-style'] = captionStyle!; + } + break; + case 'CL': + final String? callout = _getValue(primitive); + if (!isNullOrEmpty(callout)) { + table['callout'] = callout!; + } + break; + case PdfDictionaryProperties.quadPoints: + final String? coords = _getValue(primitive); + if (!isNullOrEmpty(coords)) { + table['coords'] = coords!; + } + break; + case PdfDictionaryProperties.ca: + final String? opacity = _getValue(primitive); + if (!isNullOrEmpty(opacity)) { + table['opacity'] = opacity!; + } + break; + case PdfDictionaryProperties.f: + if (primitive is PdfNumber) { + final List annotationFlags = + PdfAnnotationHelper.obtainAnnotationFlags( + primitive.value!.toInt()); + final String flag = annotationFlags + .map((PdfAnnotationFlags flag) => getEnumName(flag)) + .toString() + .replaceAll(RegExp('[ ()]'), '') + .toLowerCase(); + table[PdfDictionaryProperties.flags.toLowerCase()] = flag; + } + break; + case PdfDictionaryProperties.contents: + final IPdfPrimitive? contents = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.contents]); + if (contents != null && + contents is PdfString && + !isNullOrEmpty(contents.value)) { + table['contents'] = _getValidString(contents.value)!; + } + break; + case 'InkList': + final Map points = {}; + final IPdfPrimitive? inkList = + PdfCrossTable.dereference(dictionary['InkList']); + if (inkList != null && inkList is PdfArray && inkList.count > 0) { + final List element = []; + for (int j = 0; j < inkList.count; j++) { + if (inkList[j]! is PdfArray) { + element.add(inkList[j]! as PdfArray); + } + } + points['gesture'] = _convertToJsonArray(element); + table['inklist'] = convertToJson(points); + } + break; + case PdfDictionaryProperties.vertices: + final IPdfPrimitive? vertices = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.vertices]); + if (vertices != null && vertices is PdfArray && vertices.count > 0) { + if (vertices.count.isEven) { + String value = ''; + IPdfPrimitive? numberElement; + for (int i = 0; i < vertices.count - 1; i++) { + numberElement = vertices.elements[i]; + if (numberElement != null && numberElement is PdfNumber) { + final String? number = _getValue(numberElement); + if (!isNullOrEmpty(number)) { + value += number! + (i % 2 != 0 ? ';' : ','); + } + } + } + numberElement = vertices.elements[vertices.count - 1]; + if (numberElement != null && numberElement is PdfNumber) { + final String? number = _getValue(numberElement); + if (!isNullOrEmpty(number)) { + value += number!; + } + } + if (!isNullOrEmpty(value)) { + table['vertices'] = value; + } + } + } + break; + case 'DS': + if (dictionary.containsKey('DS')) { + final IPdfPrimitive? defaultStyle = + PdfCrossTable.dereference(dictionary['DS']); + final Map styleTable = {}; + if (defaultStyle != null && + defaultStyle is PdfString && + !isNullOrEmpty(defaultStyle.value)) { + final List textStyle = defaultStyle.value!.split(';'); + for (int i = 0; i < textStyle.length; i++) { + if (!isNullOrEmpty(textStyle[i]) && textStyle[i].contains(',')) { + textStyle[i] = _replaceJsonDelimiters(textStyle[i]); + } + final List text = textStyle[i].split(':'); + styleTable[text[0]] = text[1]; + } + } + table['defaultStyle'] = convertToJson(styleTable); + } + break; + case 'RC': + if (dictionary.containsKey('RC')) { + final IPdfPrimitive? contentStyle = + PdfCrossTable.dereference(dictionary['RC']); + if (contentStyle != null && + contentStyle is PdfString && + !isNullOrEmpty(contentStyle.value)) { + String value = contentStyle.value!; + final int index = value.indexOf(' 0) { + value = value.substring(index); + } + table['contents-richtext'] = _getValidString(value)!; + } + } + break; + case PdfDictionaryProperties.type: + case PdfDictionaryProperties.subtype: + case PdfDictionaryProperties.p: + case PdfDictionaryProperties.parent: + case PdfDictionaryProperties.l: + case PdfDictionaryProperties.fs: + case 'MeasurementTypes': + case 'GroupNesting': + case 'ITEx': + case 'Sound': + break; + case PdfDictionaryProperties.border: + case PdfDictionaryProperties.a: + case PdfDictionaryProperties.r: + case PdfDictionaryProperties.x: + final String? value = _getValue(primitive); + if (!isNullOrEmpty(value)) { + table[key.toLowerCase()] = value!; + } + break; + default: + final String? value = _getValue(primitive); + if (!isNullOrEmpty(value)) { + table[key] = value!; + } + break; + } + } + + String _convertToJsonArray(List value) { + String json = '['; + for (int i = 0; i < value.length; i++) { + final String? point = _getValue(value[i]); + if (point != null) { + json = '$json[$point]'; + } + if (i < value.length - 1) { + json = '$json,'; + } + } + json = '$json]'; + return json; + } + + String _getColor(IPdfPrimitive primitive) { + String color = ''; + if (primitive is PdfArray && primitive.count >= 3) { + final String r = PdfString.bytesToHex([ + ((primitive.elements[0]! as PdfNumber).value! * 255).round() + ]).toUpperCase(); + final String g = PdfString.bytesToHex([ + ((primitive.elements[1]! as PdfNumber).value! * 255).round() + ]).toUpperCase(); + final String b = PdfString.bytesToHex([ + ((primitive.elements[2]! as PdfNumber).value! * 255).round() + ]).toUpperCase(); + color = '#$r$g$b'; + } + return color; + } + + void _exportMeasureDictionary( + PdfDictionary dictionary, Map table) { + final IPdfPrimitive? mdictionary = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.measure]); + if (mdictionary != null && mdictionary is PdfDictionary) { + if (mdictionary.containsKey(PdfDictionaryProperties.type)) { + table['type1'] = 'Measure'; + } + if (mdictionary.containsKey(PdfDictionaryProperties.r)) { + final String? value = _getValue(mdictionary[PdfDictionaryProperties.r]); + if (!isNullOrEmpty(value)) { + table['ratevalue'] = value!; + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.subtype)) { + final String? value = + _getValue(mdictionary[PdfDictionaryProperties.subtype]); + if (!isNullOrEmpty(value)) { + table[PdfDictionaryProperties.subtype] = value!; + } + } + if (mdictionary.containsKey('TargetUnitConversion')) { + final String? value = _getValue(mdictionary['TargetUnitConversion']); + if (!isNullOrEmpty(value)) { + table['TargetUnitConversion'] = value!; + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.a)) { + final IPdfPrimitive? aArray = mdictionary[PdfDictionaryProperties.a]; + if (aArray != null && + aArray is PdfArray && + aArray.elements.isNotEmpty) { + final IPdfPrimitive? adictionary = + PdfCrossTable.dereference(aArray.elements[0]); + if (adictionary != null && adictionary is PdfDictionary) { + _exportMeasureFormatDetails('area', adictionary, table); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.d)) { + final IPdfPrimitive? dArray = mdictionary[PdfDictionaryProperties.d]; + if (dArray != null && + dArray is PdfArray && + dArray.elements.isNotEmpty) { + final IPdfPrimitive? ddictionary = + PdfCrossTable.dereference(dArray.elements[0]); + if (ddictionary != null && ddictionary is PdfDictionary) { + _exportMeasureFormatDetails('distance', ddictionary, table); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.x)) { + final IPdfPrimitive? xArray = mdictionary[PdfDictionaryProperties.x]; + if (xArray != null && + xArray is PdfArray && + xArray.elements.isNotEmpty) { + final IPdfPrimitive? xdictionary = + PdfCrossTable.dereference(xArray.elements[0]); + if (xdictionary != null && xdictionary is PdfDictionary) { + _exportMeasureFormatDetails('xformat', xdictionary, table); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.t)) { + final IPdfPrimitive? tArray = mdictionary[PdfDictionaryProperties.t]; + if (tArray != null && + tArray is PdfArray && + tArray.elements.isNotEmpty) { + final IPdfPrimitive? tdictionary = + PdfCrossTable.dereference(tArray.elements[0]); + if (tdictionary != null && tdictionary is PdfDictionary) { + _exportMeasureFormatDetails('tformat', tdictionary, table); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.v)) { + final IPdfPrimitive? vArray = mdictionary[PdfDictionaryProperties.v]; + if (vArray != null && + vArray is PdfArray && + vArray.elements.isNotEmpty) { + final IPdfPrimitive? vdictionary = + PdfCrossTable.dereference(vArray.elements[0]); + if (vdictionary != null && vdictionary is PdfDictionary) { + _exportMeasureFormatDetails('vformat', vdictionary, table); + } + } + } + } + } + + void _exportMeasureFormatDetails( + String key, PdfDictionary measurementDetails, Map table) { + final Map subTable = {}; + if (measurementDetails.containsKey(PdfDictionaryProperties.c)) { + final String? value = + _getValue(measurementDetails[PdfDictionaryProperties.c]); + if (!isNullOrEmpty(value)) { + subTable['c'] = value!; + } + } + if (measurementDetails.containsKey(PdfDictionaryProperties.f)) { + final String? value = + _getValue(measurementDetails[PdfDictionaryProperties.f]); + if (!isNullOrEmpty(value)) { + subTable['f'] = value!; + } + } + if (measurementDetails.containsKey(PdfDictionaryProperties.d)) { + final String? value = + _getValue(measurementDetails[PdfDictionaryProperties.d]); + if (!isNullOrEmpty(value)) { + subTable['d'] = value!; + } + } + if (measurementDetails.containsKey(PdfDictionaryProperties.rd)) { + final String? value = + _getValue(measurementDetails[PdfDictionaryProperties.rd]); + if (!isNullOrEmpty(value)) { + subTable['rd'] = value!; + } + } + if (measurementDetails.containsKey(PdfDictionaryProperties.u)) { + final String? value = + _getValue(measurementDetails[PdfDictionaryProperties.u]); + if (!isNullOrEmpty(value)) { + subTable['u'] = value!; + } + } + if (measurementDetails.containsKey('RT')) { + final String? value = _getValue(measurementDetails['RT']); + if (!isNullOrEmpty(value)) { + subTable['rt'] = value!; + } + } + if (measurementDetails.containsKey('SS')) { + final String? value = _getValue(measurementDetails['SS']); + if (!isNullOrEmpty(value)) { + subTable['ss'] = value!; + } + } + if (measurementDetails.containsKey('FD')) { + final String? value = _getValue(measurementDetails['FD']); + if (!isNullOrEmpty(value)) { + subTable['fd'] = value!; + } + } + if (measurementDetails.containsKey(PdfDictionaryProperties.type)) { + final String? value = + _getValue(measurementDetails[PdfDictionaryProperties.type]); + if (!isNullOrEmpty(value)) { + subTable[PdfDictionaryProperties.type] = value!; + } + } + table[key] = convertToJson(subTable); + } + + String _replaceJsonDelimiters(String value) { + // ignore: unnecessary_string_escapes + return value.contains(RegExp('[":,{}]|[\[]|]')) + ? value + .replaceAll(',', '_x002C_') + .replaceAll('"', '_x0022_') + .replaceAll(':', '_x003A_') + .replaceAll('{', '_x007B_') + .replaceAll('}', '_x007D_') + .replaceAll('[', '_x005B_') + .replaceAll(']', '_x005D_') + : value; + } + + String? _getAnnotationType(PdfDictionary dictionary) { + if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? subtype = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.subtype]); + if (subtype != null && subtype is PdfName && subtype.name != null) { + return subtype.name!; + } + } + return null; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_parser.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_parser.dart new file mode 100644 index 000000000..69181d44c --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_parser.dart @@ -0,0 +1,1227 @@ +import 'dart:convert'; + +import '../../interfaces/pdf_interface.dart'; +import '../forms/pdf_form.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../pages/pdf_page.dart'; +import '../pdf_document/pdf_document.dart'; +import '../primitives/pdf_array.dart'; +import '../primitives/pdf_boolean.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; +import 'enum.dart'; +import 'pdf_annotation.dart'; +import 'xfdf_parser.dart'; + +/// Internal class. +class JsonParser { + /// Internal Constructor. + JsonParser(this.document); + + /// Internal Field. + late PdfDocument document; + + /// Internal Field. + String? annotation; + + /// Internal Field. + String? style; + + /// Internal Field. + bool isBasicStyle = true; + + /// Internal Field. + String? beginLineStyle; + + /// Internal Field. + String? endLineStyle; + + /// Internal Field. + Map? dataStream; + + /// Internal Field. + String? values; + + /// Internal Field. + Map? groupReferences; + + /// Internal Field. + List? groupHolders; + + bool _isNormalAppearanceAdded = false; + + /// Internal Method. + void importAnnotationData(List? data) { + if (data != null) { + final Map jsonData = json.decode(utf8.decode(data)); + parseJsonData(jsonData); + jsonData.clear(); + if (groupHolders != null && groupHolders!.isNotEmpty) { + for (final PdfDictionary dictionary in groupHolders!) { + final IPdfPrimitive? inReplyTo = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.irt]); + if (inReplyTo != null && + inReplyTo is PdfString && + !isNullOrEmpty(inReplyTo.value)) { + if (groupReferences != null && + groupReferences!.containsKey(inReplyTo.value)) { + dictionary[PdfDictionaryProperties.irt] = + groupReferences![inReplyTo.value]; + } else { + dictionary.remove(PdfDictionaryProperties.irt); + } + } + } + } + if (groupReferences != null) { + groupReferences!.clear(); + } + if (groupHolders != null) { + groupHolders!.clear(); + } + groupReferences = null; + groupHolders = null; + } + } + + /// Internal Method. + void parseJsonData(Map data) { + if (data.containsKey('type')) { + parseAnnotationData(data); + } + for (final dynamic value in data.values) { + if (value is Map) { + parseJsonData(value); + } else if (value is List) { + // ignore: avoid_function_literals_in_foreach_calls + value.forEach((dynamic element) { + if (element is Map) { + parseJsonData(element); + } + }); + } + } + } + + /// Internal Method. + void parseAnnotationData(Map annotData) { + final String page = annotData.containsKey('page') ? annotData['page'] : ''; + final String type = annotData.containsKey('type') ? annotData['type'] : ''; + final int pageIndex = int.tryParse(page) ?? -1; + if (pageIndex >= 0 && pageIndex < document.pages.count) { + final PdfPage loadedPage = document.pages[pageIndex]; + PdfPageHelper.getHelper(loadedPage).importAnnotation = true; + final PdfDictionary annotDictionary = + getAnnotationData(type, pageIndex, annotData); + if (annotDictionary.count > 0) { + final PdfReferenceHolder holder = PdfReferenceHolder(annotDictionary); + if (annotDictionary.containsKey(PdfDictionaryProperties.nm) || + annotDictionary.containsKey(PdfDictionaryProperties.irt)) { + addReferenceToGroup(holder, annotDictionary); + } + final PdfDictionary? pageDictionary = + PdfPageHelper.getHelper(document.pages[pageIndex]).dictionary; + if (pageDictionary != null) { + if (!pageDictionary.containsKey(PdfDictionaryProperties.annots)) { + pageDictionary[PdfDictionaryProperties.annots] = PdfArray(); + } + final IPdfPrimitive? annots = PdfCrossTable.dereference( + pageDictionary[PdfDictionaryProperties.annots]); + if (annots != null && annots is PdfArray) { + annots.elements.add(holder); + annots.changed = true; + } + } + } + } + beginLineStyle = null; + endLineStyle = null; + } + + /// Internal Method. + void addReferenceToGroup( + PdfReferenceHolder holder, PdfDictionary dictionary) { + IPdfPrimitive? name = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.nm]); + groupReferences ??= {}; + if (name != null && name is PdfString && !isNullOrEmpty(name.value)) { + groupReferences![name.value!] = holder; + if (dictionary.containsKey(PdfDictionaryProperties.irt)) { + groupHolders ??= []; + groupHolders!.add(dictionary); + } + } else if (name == null) { + if (dictionary.containsKey(PdfDictionaryProperties.irt)) { + name = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.irt]); + } + if (name != null && name is PdfString && !isNullOrEmpty(name.value)) { + if (groupReferences!.containsKey(name.value)) { + final PdfReferenceHolder referenceHolder = + groupReferences![name.value]!; + dictionary[PdfDictionaryProperties.irt] = referenceHolder; + } + } + } + } + + /// Internal Method. + PdfDictionary getAnnotationData( + String type, int pageindex, Map annotData) { + final PdfDictionary annotDictionary = PdfDictionary(); + annotDictionary.setName( + PdfDictionaryProperties.type, PdfDictionaryProperties.annot); + bool isValidType = true; + switch (type.toLowerCase()) { + case 'line': + annotDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.line); + break; + case 'circle': + annotDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.circle); + break; + case 'square': + annotDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.square); + break; + case 'polyline': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'PolyLine'); + break; + case 'polygon': + annotDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.polygon); + break; + case 'ink': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'Ink'); + break; + case 'popup': + annotDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.popup); + break; + case 'text': + annotDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.text); + break; + case 'freetext': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'FreeText'); + break; + case 'stamp': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'Stamp'); + break; + case 'highlight': + annotDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.highlight); + break; + case 'squiggly': + annotDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.squiggly); + break; + case 'underline': + annotDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.underline); + break; + case 'strikeout': + annotDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.strikeOut); + break; + case 'fileattachment': + annotDictionary.setName( + PdfDictionaryProperties.subtype, 'FileAttachment'); + break; + case 'sound': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'Sound'); + break; + case 'redact': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'Redact'); + annotation = 'redact'; + break; + case 'caret': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'Caret'); + break; + default: + isValidType = false; + break; + } + if (isValidType) { + addAnnotationData(annotDictionary, annotData, pageindex); + } + return annotDictionary; + } + + /// Internal method. + void addAnnotationData(PdfDictionary annotDictionary, + Map annotData, int index) { + List? linePoints = []; + final PdfDictionary borderEffectDictionary = PdfDictionary(); + final PdfDictionary borderStyleDictionary = PdfDictionary(); + annotData.forEach((String key, dynamic value) { + if (value is String) { + value = PdfFormHelper.decodeXMLConversion(value); + } + switch (key.toLowerCase()) { + case 'start': + case 'end': + linePoints!.addAll(_obtainFloatPoints(value)); + if (linePoints!.length == 4) { + annotDictionary.setProperty( + PdfDictionaryProperties.l, PdfArray(linePoints)); + linePoints!.clear(); + linePoints = null; + } + break; + case 'itex': + break; + case 'state': + addString(annotDictionary, 'State', value.toString()); + break; + case 'statemodel': + addString(annotDictionary, 'StateModel', value.toString()); + break; + case 'replytype': + if (value == 'group') { + annotDictionary.setName('RT', 'Group'); + } + break; + case 'inreplyto': + addString( + annotDictionary, PdfDictionaryProperties.irt, value.toString()); + break; + case 'dashes': + case 'width': + case 'intensity': + case 'style': + addBorderStyle( + key, value, borderEffectDictionary, borderStyleDictionary); + break; + case 'rect': + final List points = _obtainFloatPoints(value.values.toList()); + if (points.length == 4) { + annotDictionary.setProperty( + PdfDictionaryProperties.rect, PdfArray(points)); + } + break; + case 'color': + if (value is String && !isNullOrEmpty(value)) { + final PdfArray? colorArray = getColorArray(value); + if (colorArray != null) { + annotDictionary.setProperty( + PdfDictionaryProperties.c, colorArray); + } + } + break; + case 'oc': + if (annotation == 'redact') { + if (value is String && !isNullOrEmpty(value)) { + final PdfArray? colorArray = getColorArray(value); + if (colorArray != null) { + annotDictionary.setProperty( + PdfDictionaryProperties.ic, colorArray); + } + } + } + break; + case 'interior-color': + if (value is String && !isNullOrEmpty(value)) { + final PdfArray? colorArray = getColorArray(value); + if (colorArray != null) { + annotDictionary.setProperty( + PdfDictionaryProperties.ic, colorArray); + } + } + break; + case 'date': + addString( + annotDictionary, PdfDictionaryProperties.m, value.toString()); + break; + case 'creationdate': + addString(annotDictionary, PdfDictionaryProperties.creationDate, + value.toString()); + break; + case 'name': + addString( + annotDictionary, PdfDictionaryProperties.nm, value.toString()); + break; + case 'icon': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.name, value); + } + break; + case 'subject': + addString( + annotDictionary, PdfDictionaryProperties.subj, value.toString()); + break; + case 'title': + addString(annotDictionary, PdfDictionaryProperties.t, value); + break; + case 'rotation': + addNumber(annotDictionary, PdfDictionaryProperties.rotate, value); + break; + case 'fringe': + addFloatPoints(annotDictionary, _obtainFloatPoints(value), + PdfDictionaryProperties.rd); + break; + case 'it': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.it, value); + } + break; + case 'leaderlength': + addNumber( + annotDictionary, PdfDictionaryProperties.ll, value.toString()); + break; + case 'leaderextend': + addNumber( + annotDictionary, PdfDictionaryProperties.lle, value.toString()); + break; + case 'caption': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setBoolean(PdfDictionaryProperties.cap, + value.toLowerCase() == 'yes' || value.toLowerCase() == 'true'); + } + break; + case 'caption-style': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.cp, value); + } + break; + case 'callout': + addFloatPoints(annotDictionary, _obtainFloatPoints(value), + PdfDictionaryProperties.cl); + break; + case 'coords': + addFloatPoints(annotDictionary, _obtainFloatPoints(value), + PdfDictionaryProperties.quadPoints); + break; + case 'border': + addFloatPoints(annotDictionary, _obtainFloatPoints(value), + PdfDictionaryProperties.border); + break; + case 'opacity': + addNumber(annotDictionary, PdfDictionaryProperties.ds, value); + break; + case 'defaultstyle': + addString( + annotDictionary, + PdfDictionaryProperties.ds, + value + .toString() + .replaceAll(RegExp(r'[{}]'), '') + .replaceAll(',', ';')); + break; + case 'defaultappearance': + addString(annotDictionary, PdfDictionaryProperties.da, + value.toString().replaceAll(RegExp(r'[{},]'), '')); + break; + case 'contents-richtext': + final String richtext = trimEscapeCharacters(value); + annotDictionary.setString(PdfDictionaryProperties.rc, richtext); + break; + case 'flags': + if (value is String && !isNullOrEmpty(value)) { + final List annotFlag = []; + if (value.contains(',')) { + final List values = value.split(','); + for (final String flag in values) { + final PdfAnnotationFlags flagType = + XfdfParser.mapAnnotationFlags(flag); + if (!annotFlag.contains(flagType)) { + annotFlag.add(flagType); + } + } + } else { + annotFlag.add(XfdfParser.mapAnnotationFlags(value)); + } + int flagValue = 0; + for (int i = 0; i < annotFlag.length; i++) { + flagValue |= + PdfAnnotationHelper.getAnnotationFlagsValue(annotFlag[i]); + } + if (flagValue > 0) { + annotDictionary.setNumber(PdfDictionaryProperties.f, flagValue); + } + } + break; + case 'open': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setBoolean(PdfDictionaryProperties.open, + value == 'true' || value == 'yes'); + } + break; + case 'repeat': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setBoolean(PdfDictionaryProperties.repeat, + value == 'true' || value == 'yes'); + } + break; + case 'overlaytext': + annotDictionary.setString(PdfDictionaryProperties.overlayText, value); + break; + case 'contents': + final String contents = trimEscapeCharacters(value); + if (!isNullOrEmpty(contents)) { + annotDictionary.setString( + PdfDictionaryProperties.contents, contents); + } + break; + case 'q': + final int? alignment = int.tryParse(value.toString()); + if (alignment != null) { + annotDictionary.setNumber(PdfDictionaryProperties.q, alignment); + } + break; + case 'inklist': + final PdfArray inkListCollection = PdfArray(); + final String inklist = value + .toString() + .replaceAll('gesture', '') + .replaceAll(RegExp(r'[\[\]{}:]'), ''); + final List pointsArray = inklist.split(','); + if (pointsArray.isNotEmpty) { + final List pointsList = []; + for (final String point in pointsArray) { + final num? result = num.tryParse(point); + if (result != null) { + pointsList.add(result); + } + } + if (pointsList.isNotEmpty && pointsList.length.isEven) { + inkListCollection.add(PdfArray(pointsList)); + } + pointsList.clear(); + } + annotDictionary.setProperty( + PdfDictionaryProperties.inkList, inkListCollection); + break; + case 'head': + beginLineStyle = + getEnumName(XfdfParser.mapLineEndingStyle(value.toString())); + break; + case 'tail': + endLineStyle = + getEnumName(XfdfParser.mapLineEndingStyle(value.toString())); + break; + case 'creation': + case 'modification': + case 'file': + case 'bits': + case 'channels': + case 'encoding': + case 'rate': + case 'length': + case 'filter': + case 'mode': + case 'size': + dataStream ??= {}; + dataStream![key] = value.toString(); + break; + case 'data': + values = value; + break; + case 'vertices': + if (value is String && !isNullOrEmpty(value)) { + final List vertices = value.split(RegExp('[;,]')); + if (vertices.isNotEmpty) { + final List verticesList = []; + for (final String vertice in vertices) { + addFloatPointsToCollection(verticesList, vertice); + } + if (verticesList.isNotEmpty && verticesList.length.isEven) { + annotDictionary.setProperty( + PdfDictionaryProperties.vertices, PdfArray(verticesList)); + } + } + } + break; + case 'customdata': + addString(annotDictionary, PdfDictionaryProperties.customData, + trimEscapeCharacters(value.toString())); + break; + case 'appearance': + _addAppearanceData(annotDictionary, value.toString()); + break; + default: + break; + } + }); + _addMeasureDictionary(annotDictionary, annotData); + if (!isNullOrEmpty(beginLineStyle)) { + if (!isNullOrEmpty(endLineStyle)) { + final PdfArray lineEndingStyles = PdfArray(); + lineEndingStyles.add(PdfName(beginLineStyle)); + lineEndingStyles.add(PdfName(endLineStyle)); + annotDictionary.setProperty( + PdfDictionaryProperties.le, lineEndingStyles); + } else { + annotDictionary.setName(PdfDictionaryProperties.le, beginLineStyle); + } + } else if (!isNullOrEmpty(endLineStyle)) { + annotDictionary.setName(PdfDictionaryProperties.le, beginLineStyle); + } + if (borderStyleDictionary.count > 0) { + borderStyleDictionary.setProperty(PdfDictionaryProperties.type, + PdfName(PdfDictionaryProperties.border)); + annotDictionary.setProperty(PdfDictionaryProperties.bs, + PdfReferenceHolder(borderStyleDictionary)); + } + if (borderEffectDictionary.count > 0) { + annotDictionary.setProperty(PdfDictionaryProperties.be, + PdfReferenceHolder(borderEffectDictionary)); + } + if (dataStream != null && values != null) { + addStreamData(dataStream!, annotDictionary, values!); + } + } + + void _addMeasureDictionary( + PdfDictionary annotDictionary, Map element) { + Map? area; + Map? distance; + Map? xformat; + Map? tformat; + Map? vformat; + final PdfDictionary measureDictionary = PdfDictionary(); + final PdfArray dArray = PdfArray(); + final PdfArray aArray = PdfArray(); + final PdfArray xArray = PdfArray(); + final PdfArray tArray = PdfArray(); + final PdfArray vArray = PdfArray(); + final PdfDictionary dDict = PdfDictionary(); + final PdfDictionary aDict = PdfDictionary(); + final PdfDictionary xDict = PdfDictionary(); + final PdfDictionary tDict = PdfDictionary(); + final PdfDictionary vDict = PdfDictionary(); + measureDictionary.items![PdfName(PdfDictionaryProperties.a)] = aArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.d)] = dArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.x)] = xArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.t)] = tArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.v)] = vArray; + if (element.containsKey(PdfDictionaryProperties.type1.toLowerCase())) { + measureDictionary.setName( + PdfDictionaryProperties.type, PdfDictionaryProperties.measure); + } + element.forEach((String key, dynamic value) { + if (value is String) { + switch (key.toLowerCase()) { + case 'ratevalue': + measureDictionary.setString(PdfDictionaryProperties.r, value); + break; + case 'subtype': + measureDictionary.setString(PdfDictionaryProperties.subtype, value); + break; + case 'targetunitconversion': + measureDictionary.setString( + PdfDictionaryProperties.targetUnitConversion, value); + break; + case 'area': + area = {}; + area = _addDictionaryData(area!, value); + break; + case 'distance': + distance = {}; + distance = _addDictionaryData(distance!, value); + break; + case 'xformat': + xformat = {}; + xformat = _addDictionaryData(xformat!, value); + break; + case 'tformat': + tformat = {}; + tformat = _addDictionaryData(tformat!, value); + break; + case 'vformat': + vformat = {}; + vformat = _addDictionaryData(vformat!, value); + break; + } + } + }); + if (xformat != null) { + _addElements(xformat!, xDict); + xArray.add(xDict); + } + if (area != null) { + _addElements(area!, aDict); + aArray.add(aDict); + } + if (distance != null) { + _addElements(distance!, dDict); + dArray.add(dDict); + } + if (vformat != null) { + _addElements(vformat!, vDict); + vArray.add(vDict); + } + if (tformat != null) { + _addElements(tformat!, tDict); + tArray.add(tDict); + } + if (measureDictionary.count > 0 && + measureDictionary.containsKey(PdfDictionaryProperties.type)) { + annotDictionary.items![PdfName(PdfDictionaryProperties.measure)] = + PdfReferenceHolder(measureDictionary); + } + } + + void _addElements(Map element, PdfDictionary dictionary) { + element.forEach((String key, String value) { + final num? elementValue = num.tryParse(value); + if (elementValue != null) { + switch (key.toLowerCase()) { + case 'd': + dictionary.items![PdfName(PdfDictionaryProperties.d)] = + PdfNumber(elementValue); + break; + case 'c': + dictionary.items![PdfName(PdfDictionaryProperties.c)] = + PdfNumber(elementValue); + break; + case 'rt': + dictionary.items![PdfName(PdfDictionaryProperties.rt)] = + PdfNumber(elementValue); + break; + case 'rd': + dictionary.items![PdfName(PdfDictionaryProperties.rd)] = + PdfNumber(elementValue); + break; + case 'ss': + dictionary.items![PdfName(PdfDictionaryProperties.ss)] = + PdfNumber(elementValue); + break; + case 'u': + dictionary.items![PdfName(PdfDictionaryProperties.u)] = + PdfNumber(elementValue); + break; + case 'f': + dictionary.items![PdfName(PdfDictionaryProperties.f)] = + PdfNumber(elementValue); + break; + case 'fd': + dictionary.items![PdfName(PdfDictionaryProperties.fd)] = + PdfNumber(elementValue); + break; + case 'type': + dictionary.items![PdfName(PdfDictionaryProperties.type)] = + PdfNumber(elementValue); + break; + } + } + }); + } + + Map _addDictionaryData( + Map data, String value) { + String addValue = ''; + for (int k = 0; k < value.length; k++) { + addValue += (value[k] == ':' || value[k] == ';') ? '#' : value[k]; + } + final List valueSplit = addValue.split('#'); + for (int i = 0; i < valueSplit.length - 1; i += 2) { + data[valueSplit[i]] = valueSplit[i + 1]; + } + return data; + } + + void _addAppearanceData(PdfDictionary dictionary, String value) { + if (!isNullOrEmpty(value)) { + final List appearanceData = base64.decode(value); + if (appearanceData.isNotEmpty) { + final Map dict = + json.decode(utf8.decode(appearanceData)); + final PdfDictionary appearance = PdfDictionary(); + if (dict.isNotEmpty) { + for (final dynamic dictValue in dict.values) { + dictionary[PdfDictionaryProperties.ap] = PdfReferenceHolder( + _parseDictionaryItems(dictValue, appearance)); + } + } + } + } + _isNormalAppearanceAdded = false; + } + + IPdfPrimitive _parseDictionaryItems( + dynamic elementValue, IPdfPrimitive primitive) { + if (elementValue != null) { + if (elementValue is Map) { + for (String token in elementValue.keys) { + final dynamic value = elementValue[token]; + token = PdfFormHelper.decodeXMLConversion(token); + switch (token) { + case 'stream': + PdfStream stream = PdfStream(); + stream = _parseDictionaryItems(value, stream) as PdfStream; + return stream; + case 'array': + PdfArray array = PdfArray(); + array = _parseDictionaryItems(value, array) as PdfArray; + return array; + case 'name': + return PdfName(value.toString()); + case 'string': + return PdfString(value.toString()); + case 'boolean': + final bool test = value.toString().toLowerCase() == 'true'; + return PdfBoolean(test); + case 'dict': + PdfDictionary pdfDictionary = PdfDictionary(); + pdfDictionary = + _parseDictionaryItems(value, pdfDictionary) as PdfDictionary; + return pdfDictionary; + case 'int': + final int? result = int.tryParse(value.toString()); + if (result != null) { + return PdfNumber(result); + } + break; + case 'fixed': + final num? result = num.tryParse(value.toString()); + if (result != null) { + return PdfNumber(result); + } + break; + case 'data': + if (primitive is PdfStream && + value != null && + value is Map) { + primitive.dataStream = _getStreamData(value); + if (!primitive.containsKey(PdfDictionaryProperties.type) && + !primitive.containsKey(PdfDictionaryProperties.subtype)) { + primitive.decompress(); + } + bool isImage = false; + if (primitive.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? subtype = PdfCrossTable.dereference( + primitive[PdfDictionaryProperties.subtype]); + if (subtype != null && + subtype is PdfName && + subtype.name == PdfDictionaryProperties.image) { + isImage = true; + } + } + if (isImage) { + primitive.compress = false; + } else { + if (primitive.containsKey(PdfDictionaryProperties.length)) { + primitive.remove(PdfDictionaryProperties.length); + } + if (primitive.containsKey(PdfDictionaryProperties.filter)) { + primitive.remove(PdfDictionaryProperties.filter); + } + } + } + break; + case 'N': + if (_isNormalAppearanceAdded && primitive is PdfDictionary) { + primitive[token] = _parseDictionaryItems(value, primitive); + } else { + _isNormalAppearanceAdded = true; + final PdfDictionary dic = PdfDictionary(); + dic[token] = + PdfReferenceHolder(_parseDictionaryItems(value, dic)); + return dic; + } + break; + case 'BBox': + case 'Type': + case 'Subtype': + case 'Resources': + case 'BaseFont': + case 'ProcSet': + case 'Font': + case 'Encoding': + case 'Matrix': + case 'Length': + case 'CIDToGIDMap': + case 'DW': + case 'FontName': + case 'Flags': + case 'FontBBox': + case 'MissingWidth': + case 'StemV': + case 'ItalicAngle': + case 'CapHeight': + case 'Ascent': + case 'Descent': + case 'Leading': + case 'AvgWidth': + case 'MaxWidth': + case 'StemH': + case 'CIDSystemInfo': + case 'Registry': + case 'Ordering': + case 'Supplement': + case 'W': + case 'XObject': + case 'Filter': + case 'BitsPerComponent': + case 'ColorSpace': + case 'FormType': + case 'Name': + case 'Height': + case 'Width': + case 'Decode': + case 'DecodeParms': + case 'BlackIs1': + case 'Columns': + case 'K': + case 'Rows': + case 'ImageMask': + case 'Interpolate': + case 'ca': + case 'CA': + case 'AIS': + case 'BM': + case 'ExtGState': + case 'Pattern': + case 'PatternType': + case 'CS': + case 'I': + case 'S': + case 'Coords': + case 'Extend': + case 'ShadingType': + case 'Bounds': + case 'Domain': + case 'Encode': + case 'FunctionType': + case 'Widths': + case 'FirstChar': + case 'LastChar': + if (primitive is PdfDictionary) { + primitive[token] = _parseDictionaryItems(value, primitive); + } + break; + default: + if (primitive is PdfDictionary) { + final PdfDictionary temp = PdfDictionary(); + primitive[token] = + PdfReferenceHolder(_parseDictionaryItems(value, temp)); + } + break; + } + } + } else if (elementValue is List && primitive is PdfArray) { + final List list = elementValue; + for (int i = 0; i < list.length; i++) { + final dynamic listObject = list[i]; + if (listObject is Map) { + listObject.forEach((String token, dynamic value) { + token = PdfFormHelper.decodeXMLConversion(token); + switch (token) { + case 'int': + final int? result = int.tryParse(value.toString()); + if (result != null) { + primitive.add(PdfNumber(result)); + } + break; + case 'fixed': + final num? result = num.tryParse(value.toString()); + if (result != null) { + primitive.add(PdfNumber(result)); + } + break; + case 'name': + primitive.add(PdfName(value.toString())); + break; + case 'string': + primitive.add(PdfString(value.toString())); + break; + case 'dict': + PdfDictionary pdfDictionary = PdfDictionary(); + pdfDictionary = _parseDictionaryItems(value, pdfDictionary) + as PdfDictionary; + primitive.add(PdfReferenceHolder(pdfDictionary)); + break; + case 'array': + PdfArray array = PdfArray(); + array = _parseDictionaryItems(value, array) as PdfArray; + primitive.add(array); + break; + case 'boolean': + final bool test = value.toString().toLowerCase() == 'true'; + primitive.add(PdfBoolean(test)); + break; + case 'stream': + PdfStream stream = PdfStream(); + stream = _parseDictionaryItems(value, stream) as PdfStream; + primitive.add(stream); + break; + } + }); + } + } + } + } + return primitive; + } + + List? _getStreamData(Map element) { + List? rawData; + String encoding = ''; + for (final String token in element.keys) { + final dynamic value = element[token]; + switch (token) { + case 'encoding': + encoding = value.toString(); + break; + case 'bytes': + if (value != null) { + if (encoding == 'hex') { + rawData = getBytes(value.toString()); + } else if (encoding == 'ascii') { + rawData = utf8.encode(value.toString()); + } + } + return rawData; + } + } + return rawData; + } + + /// Internal Methods. + void addString(PdfDictionary dictionary, String key, String value) { + value = PdfFormHelper.decodeXMLConversion(value); + if (!isNullOrEmpty(value)) { + dictionary.setString(key, value); + } + } + + /// Internal Methods. + void addNumber(PdfDictionary dictionary, String key, String value) { + final num? number = num.tryParse(value); + if (number != null) { + dictionary.setNumber(key, number); + } + } + + /// Internal Methods. + void addBorderStyle( + String key, + dynamic value, + PdfDictionary borderEffectDictionary, + PdfDictionary borderStyleDictionary) { + if (value is String) { + switch (value) { + case 'dash': + style = PdfDictionaryProperties.d; + break; + case 'solid': + style = PdfDictionaryProperties.s; + break; + case 'bevelled': + style = PdfDictionaryProperties.b; + break; + case 'inset': + style = PdfDictionaryProperties.i; + break; + case 'underline': + style = PdfDictionaryProperties.u; + break; + case 'cloudy': + style = PdfDictionaryProperties.c; + isBasicStyle = false; + break; + } + } + if (key == 'width') { + final double? width = double.tryParse(value); + if (width != null) { + borderStyleDictionary.setNumber(PdfDictionaryProperties.w, width); + } + } + if (key == 'intensity') { + final double? intensity = double.tryParse(value); + if (intensity != null) { + borderEffectDictionary.setNumber(PdfDictionaryProperties.i, intensity); + } + } + if (!isNullOrEmpty(style)) { + (isBasicStyle ? borderStyleDictionary : borderEffectDictionary) + .setName(PdfDictionaryProperties.s, style); + } + if (key == 'dashes') { + final List dashPoints = _obtainFloatPoints(value.toString()); + if (dashPoints.isNotEmpty) { + borderStyleDictionary.setProperty( + PdfDictionaryProperties.d, PdfArray(dashPoints)); + } + } + } + + /// Internal Methods. + PdfArray? getColorArray(String value) { + if (!value.contains(',')) { + final String hex = value.replaceAll('#', ''); + final int r = int.parse(hex.substring(0, 2), radix: 16); + final int g = int.parse(hex.substring(2, 4), radix: 16); + final int b = int.parse(hex.substring(4, hex.length), radix: 16); + if (r >= 0 && g >= 0 && b >= 0) { + final PdfArray colorArray = PdfArray(); + colorArray.add(PdfNumber(r / 255)); + colorArray.add(PdfNumber(g / 255)); + colorArray.add(PdfNumber(b / 255)); + return colorArray; + } + } else { + final List colorValues = value.split(','); + final num? r = num.tryParse(colorValues[0]); + final num? g = num.tryParse(colorValues[1]); + final num? b = num.tryParse(colorValues[2]); + if (r != null && g != null && b != null) { + final PdfArray colorArray = PdfArray(); + colorArray.add(PdfNumber(r)); + colorArray.add(PdfNumber(g)); + colorArray.add(PdfNumber(b)); + return colorArray; + } + } + return null; + } + + /// Internal Methods. + void addFloatPoints(PdfDictionary dictionary, List? points, String key) { + if (points != null && points.isNotEmpty) { + dictionary.setProperty(key, PdfArray(points)); + } + } + + /// Internal Methods. + String trimEscapeCharacters(String value) { + if (value.contains(r'\\r')) { + value = value.replaceAll(r'\\r', '\r'); + } + if (value.contains(r'\\n')) { + value = value.replaceAll(r'\\n', '\n'); + } + if (value.contains(r'\\\"')) { + value = value.replaceAll(r'\\\"', '"'); + } + return value; + } + + /// Internal Methods. + void addFloatPointsToCollection(List collection, String value) { + final num? number = num.tryParse(value); + if (number != null) { + collection.add(number); + } + } + + /// Internal Methods. + void addStreamData(Map dataValues, + PdfDictionary annotDictionary, String values) { + if (annotDictionary.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? primitive = PdfCrossTable.dereference( + annotDictionary[PdfDictionaryProperties.subtype]); + if (primitive != null && primitive is PdfName && primitive.name != null) { + final String subtype = primitive.name!; + final List raw = getBytes(values); + if (raw.isNotEmpty) { + if (subtype == 'sound') { + final PdfStream soundStream = PdfStream(); + soundStream.setName( + PdfDictionaryProperties.type, PdfDictionaryProperties.sound); + dataValues.forEach((String key, String value) { + switch (key) { + case 'bits': + case 'rate': + case 'channels': + addNumber(soundStream, key, value); + break; + case 'encoding': + if (!isNullOrEmpty(value)) { + soundStream.setName(PdfDictionaryProperties.e, value); + } + break; + case 'filter': + soundStream.addFilter(PdfDictionaryProperties.flateDecode); + break; + } + }); + soundStream.dataStream = raw; + annotDictionary.setProperty( + PdfDictionaryProperties.sound, PdfReferenceHolder(soundStream)); + } else if (subtype == 'FileAttachment') { + final PdfDictionary fileDictionary = PdfDictionary(); + final PdfStream fileStream = PdfStream(); + final PdfDictionary param = PdfDictionary(); + fileDictionary.setName( + PdfDictionaryProperties.type, PdfDictionaryProperties.filespec); + dataValues.forEach((String key, String value) { + switch (key) { + case 'file': + addString(fileDictionary, PdfDictionaryProperties.f, value); + addString(fileDictionary, PdfDictionaryProperties.uf, value); + break; + case 'size': + final int? size = int.tryParse(value); + if (size != null) { + param.setNumber(PdfDictionaryProperties.size, size); + fileStream.setNumber('DL', size); + } + break; + case 'creation': + addString( + param, 'creation', PdfDictionaryProperties.creationDate); + break; + case 'modification': + addString(param, 'modification', + PdfDictionaryProperties.modificationDate); + break; + } + }); + fileStream.setProperty(PdfDictionaryProperties.params, param); + fileStream.dataStream = raw; + fileStream.addFilter(PdfDictionaryProperties.flateDecode); + final PdfDictionary embeddedFile = PdfDictionary(); + embeddedFile.setProperty( + PdfDictionaryProperties.f, PdfReferenceHolder(fileStream)); + fileDictionary.setProperty( + PdfDictionaryProperties.ef, embeddedFile); + annotDictionary.setProperty( + PdfDictionaryProperties.fs, PdfReferenceHolder(fileDictionary)); + } + } + } + } + } + + /// Internal Methods. + List getBytes(String hex) { + final PdfString pdfString = PdfString(''); + return pdfString.hexToBytes(hex); + } + + List _obtainFloatPoints(dynamic points) { + final List pointsValue = + points.toString().replaceAll(RegExp(r'[\[\]{}:]'), '').split(','); + final List linePoints = []; + for (final dynamic value in pointsValue) { + if (value is String && !isNullOrEmpty(value)) { + final num? number = num.tryParse(value); + if (number != null) { + linePoints.add(number); + } + } + } + return linePoints; + } +} + +/// Internal method. +bool isNullOrEmpty(String? value) { + return value == null || value.isEmpty; +} + +/// Internal method. +String getEnumName(dynamic text) { + final int index = text.toString().indexOf('.'); + final String name = text.toString().substring(index + 1); + return name[0].toUpperCase() + name.substring(1); +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/xfdf_parser.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/xfdf_parser.dart new file mode 100644 index 000000000..8d750dd10 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/xfdf_parser.dart @@ -0,0 +1,1351 @@ +import 'dart:convert'; +import 'dart:ui'; + +import 'package:xml/xml.dart'; + +import '../../interfaces/pdf_interface.dart'; +import '../forms/pdf_xfdf_document.dart'; +import '../graphics/figures/pdf_template.dart'; +import '../graphics/images/pdf_bitmap.dart'; +import '../graphics/pdf_transformation_matrix.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../pages/pdf_page.dart'; +import '../pdf_document/pdf_document.dart'; +import '../primitives/pdf_array.dart'; +import '../primitives/pdf_boolean.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; +import 'enum.dart'; +import 'json_parser.dart'; +import 'pdf_annotation.dart'; + +/// The class provides methods and properties to handle the loaded annotations from the existing PDF document for Xfdf export and import. +class XfdfParser { + //Consructor + /// Initializes a new instance of the [XfdfParser] class. + XfdfParser(List data, PdfDocument document) { + _document = document; + final String xmlString = utf8.decode(data); + _xmlDocument = XmlDocument.parse(xmlString); + if (_xmlDocument.rootElement.localName.toLowerCase() != 'xfdf') { + throw ArgumentError.value( + _xmlDocument.rootElement.localName, 'xmlString', 'Invalid XFDF data'); + } + } + + //Fields + late PdfDocument _document; + late XmlDocument _xmlDocument; + Map? _groupReferences; + List? _groupHolders; + + //Implementation. + /// internal method. + void parseAndImportAnnotationData() { + for (final XmlNode node + in _xmlDocument.rootElement.firstElementChild!.children) { + if (node is XmlElement) { + _parseAnnotationData(node); + } + } + } + + void _parseAnnotationData(XmlElement element) { + if (element.attributes.isNotEmpty) { + final String? pageIndexString = element.getAttribute('page'); + if (pageIndexString != null && !isNullOrEmpty(pageIndexString)) { + final int pageIndex = int.parse(pageIndexString); + if (pageIndex >= 0 && pageIndex < _document.pages.count) { + PdfPageHelper.getHelper(_document.pages[pageIndex]).importAnnotation = + true; + final PdfDictionary annotationDictionary = + _getAnnotation(element, pageIndex); + if (annotationDictionary.count > 0) { + final PdfReferenceHolder holder = + PdfReferenceHolder(annotationDictionary); + if (annotationDictionary.containsKey('NM') || + annotationDictionary.containsKey(PdfDictionaryProperties.irt)) { + _addReferenceToGroup(holder, annotationDictionary); + } + final PdfDictionary? pageDictionary = + PdfPageHelper.getHelper(_document.pages[pageIndex]).dictionary; + if (pageDictionary != null) { + if (!pageDictionary.containsKey(PdfDictionaryProperties.annots)) { + pageDictionary[PdfDictionaryProperties.annots] = PdfArray(); + } + final IPdfPrimitive? annots = PdfCrossTable.dereference( + pageDictionary[PdfDictionaryProperties.annots]); + if (annots != null && annots is PdfArray) { + annots.elements.add(holder); + _handlePopUp(annots, holder, annotationDictionary); + annots.changed = true; + } + } + } + } + } + } + } + + void _handlePopUp(PdfArray annots, PdfReferenceHolder holder, + PdfDictionary annotDictionary) { + if (annotDictionary.containsKey(PdfDictionaryProperties.popup)) { + final IPdfPrimitive? popup = PdfCrossTable.dereference( + annotDictionary[PdfDictionaryProperties.popup]); + if (popup != null && popup is PdfDictionary) { + popup.setProperty(PdfDictionaryProperties.parent, holder); + annots.add(popup); + } + } + } + + PdfDictionary _getAnnotation(XmlElement element, int pageIndex) { + final PdfDictionary annotationDictionary = PdfDictionary(); + annotationDictionary.setName( + PdfName(PdfDictionaryProperties.name), PdfDictionaryProperties.annot); + bool isValidType = true; + if (!isNullOrEmpty(element.localName)) { + switch (element.localName.toLowerCase()) { + case 'line': + annotationDictionary.setName(PdfName(PdfDictionaryProperties.subtype), + PdfDictionaryProperties.line); + final String? start = element.getAttribute('start'); + final String? end = element.getAttribute('end'); + if (start != null && end != null) { + final List linePoints = []; + _addLinePoints(linePoints, start); + _addLinePoints(linePoints, end); + if (linePoints.length == 4) { + annotationDictionary.setProperty( + PdfDictionaryProperties.l, PdfArray(linePoints)); + } + linePoints.clear(); + } + _addLineEndStyle(element, annotationDictionary); + break; + case 'circle': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.circle); + break; + case 'square': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.square); + break; + case 'polyline': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, 'PolyLine'); + _addLineEndStyle(element, annotationDictionary); + break; + case 'polygon': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.polygon); + _addLineEndStyle(element, annotationDictionary); + break; + case 'ink': + annotationDictionary.setName(PdfDictionaryProperties.subtype, 'Ink'); + break; + case 'popup': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.popup); + break; + case 'text': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, PdfDictionaryProperties.text); + break; + case 'freetext': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, 'FreeText'); + _addLineEndStyle(element, annotationDictionary); + break; + case 'stamp': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, 'Stamp'); + break; + case 'highlight': + annotationDictionary.setName(PdfDictionaryProperties.subtype, + PdfDictionaryProperties.highlight); + break; + case 'squiggly': + annotationDictionary.setName(PdfDictionaryProperties.subtype, + PdfDictionaryProperties.squiggly); + break; + case 'underline': + annotationDictionary.setName(PdfDictionaryProperties.subtype, + PdfDictionaryProperties.underline); + break; + case 'strikeout': + annotationDictionary.setName(PdfDictionaryProperties.subtype, + PdfDictionaryProperties.strikeOut); + break; + case 'fileattachment': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, 'FileAttachment'); + break; + case 'sound': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, 'Sound'); + break; + case 'caret': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, 'Caret'); + break; + case 'redact': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, 'Redact'); + break; + default: + isValidType = false; + break; + } + if (isValidType) { + _addAnnotationData(annotationDictionary, element, pageIndex); + } + } + return annotationDictionary; + } + + void _addAnnotationData( + PdfDictionary annotDictionary, XmlElement element, int pageIndex) { + _addBorderStyle(annotDictionary, element); + _applyAttributeValues(annotDictionary, element); + _parseInnerElements(annotDictionary, element, pageIndex); + _addMeasureDictionary(annotDictionary, element); + } + + void _addBorderStyle(PdfDictionary annotDictionary, XmlElement element) { + if (element.attributes.isNotEmpty) { + final PdfDictionary borderEffectDictionary = PdfDictionary(); + final PdfDictionary borderStyleDictionary = PdfDictionary(); + String? attribute = element.getAttribute('width'); + if (!isNullOrEmpty(attribute)) { + borderStyleDictionary.setNumber( + PdfDictionaryProperties.w, double.parse(attribute!)); + } + bool isBasicStyle = true; + attribute = element.getAttribute('style'); + if (!isNullOrEmpty(attribute)) { + String style = ''; + switch (attribute) { + case 'dash': + style = PdfDictionaryProperties.d; + break; + case 'solid': + style = PdfDictionaryProperties.s; + break; + case 'bevelled': + style = 'B'; + break; + case 'inset': + style = PdfDictionaryProperties.i; + break; + case 'underline': + style = PdfDictionaryProperties.u; + break; + case 'cloudy': + style = PdfDictionaryProperties.c; + isBasicStyle = false; + break; + } + if (!isNullOrEmpty(style)) { + (isBasicStyle ? borderStyleDictionary : borderEffectDictionary) + .setName(PdfDictionaryProperties.s, style); + attribute = element.getAttribute('intensity'); + if (!isBasicStyle && !isNullOrEmpty(attribute)) { + borderEffectDictionary.setNumber( + PdfDictionaryProperties.i, double.parse(attribute!)); + } else { + attribute = element.getAttribute('dashes'); + if (!isNullOrEmpty(attribute)) { + final List dashPoints = _obtainFloatPoints(attribute!); + if (dashPoints.isNotEmpty) { + borderStyleDictionary.setProperty( + PdfDictionaryProperties.d, PdfArray(dashPoints)); + } + } + } + } + } + if (borderEffectDictionary.count > 0) { + annotDictionary.setProperty(PdfDictionaryProperties.be, + PdfReferenceHolder(borderEffectDictionary)); + } else { + borderEffectDictionary.clear(); + borderEffectDictionary.isSaving = false; + } + if (borderStyleDictionary.count > 0) { + borderStyleDictionary.setProperty(PdfDictionaryProperties.type, + PdfName(PdfDictionaryProperties.border)); + annotDictionary.setProperty(PdfDictionaryProperties.bs, + PdfReferenceHolder(borderStyleDictionary)); + } else { + borderStyleDictionary.clear(); + borderStyleDictionary.isSaving = false; + } + } + } + + List _obtainFloatPoints(String value) { + final List result = []; + if (!isNullOrEmpty(value)) { + final List pointsArray = value.split(','); + for (final String pointString in pointsArray) { + final double? point = double.tryParse(pointString); + if (point != null) { + result.add(point); + } + } + } + return result; + } + + void _addLinePoints(List linePoints, String value) { + if (!isNullOrEmpty(value) && value.contains(',')) { + final List pointsArray = value.split(','); + for (final String pointString in pointsArray) { + final double? point = double.tryParse(pointString); + if (point != null) { + linePoints.add(point); + } + } + } + } + + void _applyAttributeValues( + PdfDictionary annotDictionary, XmlElement element) { + for (final XmlAttribute attribute in element.attributes) { + final String value = attribute.value; + final XmlName name = attribute.name; + switch (name.local.toLowerCase()) { + case 'page': + case 'start': + case 'end': + case 'width': + case 'head': + case 'tail': + case 'style': + case 'intensity': + case 'itex': + break; + case 'state': + _addString(annotDictionary, 'State', value); + break; + case 'statemodel': + _addString(annotDictionary, 'StateModel', value); + break; + case 'replytype': + if (value == 'group') { + annotDictionary.setName('RT', 'Group'); + } + break; + case 'inreplyto': + _addString(annotDictionary, PdfDictionaryProperties.irt, value); + break; + case 'rect': + final List points = _obtainFloatPoints(value); + if (points.isNotEmpty && points.length == 4) { + annotDictionary.setProperty( + PdfDictionaryProperties.rect, PdfArray(points)); + } + break; + case 'color': + if (!isNullOrEmpty(value)) { + final PdfArray? colorArray = _getColorArray(value); + if (colorArray != null) { + annotDictionary.setProperty( + PdfDictionaryProperties.c, colorArray); + } + } + break; + case 'interior-color': + if (!isNullOrEmpty(value)) { + final PdfArray? colorArray = _getColorArray(value); + if (colorArray != null) { + annotDictionary.setProperty( + PdfDictionaryProperties.ic, colorArray); + } + } + break; + case 'date': + _addString(annotDictionary, PdfDictionaryProperties.m, value); + break; + case 'creationdate': + _addString( + annotDictionary, PdfDictionaryProperties.creationDate, value); + break; + case 'name': + _addString(annotDictionary, 'NM', value); + break; + case 'icon': + if (!isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.name, value); + } + break; + case 'subject': + _addString(annotDictionary, PdfDictionaryProperties.subj, + _getFormattedString(value)); + break; + case 'title': + _addString(annotDictionary, PdfDictionaryProperties.t, + _getFormattedString(value)); + break; + case 'rotation': + _addInt(annotDictionary, PdfDictionaryProperties.rotate, value); + break; + case 'justification': + _addInt(annotDictionary, PdfDictionaryProperties.q, value); + break; + case 'fringe': + _addFloatPoints(annotDictionary, _obtainFloatPoints(value), + PdfDictionaryProperties.rd); + break; + case 'it': + if (!isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.it, value); + } + break; + case 'leaderlength': + _addFloat(annotDictionary, PdfDictionaryProperties.ll, value); + break; + case 'leaderextend': + if (!isNullOrEmpty(value)) { + final double? leaderExtend = double.tryParse(value); + if (leaderExtend != null) { + annotDictionary.setNumber( + PdfDictionaryProperties.lle, leaderExtend); + } + } + break; + case 'caption': + if (!isNullOrEmpty(value)) { + annotDictionary.setBoolean( + PdfDictionaryProperties.cap, value.toLowerCase() == 'yes'); + } + break; + case 'caption-style': + if (!isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.cp, value); + } + break; + case 'callout': + _addFloatPoints(annotDictionary, _obtainFloatPoints(value), 'CL'); + break; + case 'coords': + _addFloatPoints(annotDictionary, _obtainFloatPoints(value), + PdfDictionaryProperties.quadPoints); + break; + case 'border': + _addFloatPoints(annotDictionary, _obtainFloatPoints(value), + PdfDictionaryProperties.border); + break; + case 'opacity': + if (!isNullOrEmpty(value)) { + final double? opacity = double.tryParse(value); + if (opacity != null) { + annotDictionary.setNumber(PdfDictionaryProperties.ca, opacity); + } + } + break; + case 'flags': + if (!isNullOrEmpty(value)) { + final List annotationFlags = + []; + final List flags = value.split(','); + for (int i = 0; i < flags.length; i++) { + final PdfAnnotationFlags flagType = mapAnnotationFlags(flags[i]); + if (!annotationFlags.contains(flagType)) { + annotationFlags.add(flagType); + } + } + int flagValue = 0; + for (int i = 0; i < annotationFlags.length; i++) { + flagValue |= PdfAnnotationHelper.getAnnotationFlagsValue( + annotationFlags[i]); + } + if (flagValue > 0) { + annotDictionary.setNumber(PdfDictionaryProperties.f, flagValue); + } + } + break; + case 'open': + if (!isNullOrEmpty(value)) { + annotDictionary.setBoolean(PdfDictionaryProperties.open, + value == 'true' || value == 'yes'); + } + break; + case 'calibrate': + _addString(annotDictionary, 'Calibrate', value); + break; + case 'customdata': + _addString(annotDictionary, 'CustomData', value); + break; + case 'overlaytext': + _addString(annotDictionary, 'OverlayText', value); + break; + case 'repeat': + annotDictionary.setBoolean( + 'Repeat', value == 'true' || value == 'yes'); + break; + default: + break; + } + } + } + + void _parseInnerElements( + PdfDictionary annotDictionary, XmlElement element, int pageIndex) { + if (element.attributes.isNotEmpty) { + for (final XmlNode childNode in element.children) { + if (childNode is XmlElement) { + final XmlName childName = childNode.name; + switch (childName.local.toLowerCase()) { + case 'popup': + if (childNode.attributes.isNotEmpty) { + final PdfDictionary popupDictionary = + _getAnnotation(childNode, pageIndex); + if (popupDictionary.count > 0) { + final PdfReferenceHolder holder = + PdfReferenceHolder(popupDictionary); + annotDictionary.setProperty( + PdfDictionaryProperties.popup, holder); + if (popupDictionary.containsKey('NM')) { + _addReferenceToGroup(holder, popupDictionary); + } + } + } + break; + case 'contents': + String? contents = childNode.innerText; + if (!isNullOrEmpty(contents)) { + contents = contents.replaceAll('<', '<'); + contents = contents.replaceAll('>', '>'); + annotDictionary.setString( + PdfDictionaryProperties.contents, contents); + } + break; + case 'contents-richtext': + final XmlNode? richText = childNode.firstChild; + if (richText != null) { + final String richTextContents = richText.innerText; + final String contentText = childNode.innerText; + if (!isNullOrEmpty(richTextContents) && + !isNullOrEmpty(contentText) && + !annotDictionary + .containsKey(PdfDictionaryProperties.contents)) { + annotDictionary.setString( + 'RC', '$richTextContents'); + annotDictionary.setString( + PdfDictionaryProperties.contents, contentText); + } else if (!isNullOrEmpty(richTextContents)) { + annotDictionary.setString( + 'RC', '$richTextContents'); + } + } + break; + case 'defaultstyle': + _addString(annotDictionary, 'DS', childNode.innerText); + break; + case 'defaultappearance': + _addString(annotDictionary, PdfDictionaryProperties.da, + childNode.innerText); + break; + case 'vertices': + if (!isNullOrEmpty(childNode.innerText)) { + final String verticesValue = + childNode.innerText.replaceAll(';', ','); + if (verticesValue != '') { + final List verticesList = []; + _addLinePoints(verticesList, verticesValue); + if (verticesList.isNotEmpty && verticesList.length.isEven) { + annotDictionary.setProperty( + PdfDictionaryProperties.vertices, + PdfArray(verticesList)); + } + } + } + break; + case 'appearance': + if (!isNullOrEmpty(childNode.innerText)) { + final List appearanceArray = + base64.decode(childNode.innerText); + if (appearanceArray.isNotEmpty) { + final XmlDocument appearanceDoc = + XmlDocument.parse(utf8.decode(appearanceArray)); + final List childNodes = appearanceDoc.children; + for (final XmlNode rootElement in childNodes) { + if (rootElement is XmlElement) { + if (rootElement.childElements.isNotEmpty) { + final XmlName rootName = rootElement.name; + if (rootName.local == XfdfProperties.dict) { + final String? rootAttribute = + rootElement.getAttribute(XfdfProperties.key); + if (rootAttribute != null && + !isNullOrEmpty(rootAttribute)) { + if (rootAttribute == 'AP') { + final PdfDictionary appearance = PdfDictionary(); + final List childs = rootElement.children; + for (final XmlNode child in childs) { + if (child is XmlElement) { + _getAppearance(appearance, child); + } + } + if (appearance.count > 0) { + annotDictionary.setProperty( + PdfDictionaryProperties.ap, appearance); + } + } + } + } + } + } + } + } + } + break; + case 'imagedata': + if (!isNullOrEmpty(childNode.innerText)) { + _addImageToAppearance(annotDictionary, childNode.innerText); + } + break; + case 'inklist': + if (childNode.children.isNotEmpty) { + for (final XmlNode child in childNode.children) { + if (child is XmlElement) { + final XmlName childNodeName = child.name; + if (childNodeName.local.toLowerCase() == 'gesture' && + !isNullOrEmpty(child.innerText)) { + final String pointsArrayValue = + child.innerText.replaceAll(';', ','); + if (pointsArrayValue != '') { + final PdfArray inkListCollection = PdfArray(); + final List pointsList = []; + _addLinePoints(pointsList, pointsArrayValue); + if (pointsList.isNotEmpty && pointsList.length.isEven) { + inkListCollection.add(PdfArray(pointsList)); + } + pointsList.clear(); + if (inkListCollection.count > 0) { + annotDictionary.setProperty( + 'InkList', inkListCollection); + } + } + } + } + } + } + break; + case 'data': + if (!isNullOrEmpty(childNode.innerText)) { + final List raw = _hexToBytes(childNode.innerText); + if (raw.isNotEmpty) { + if (annotDictionary + .containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? subtype = PdfCrossTable.dereference( + annotDictionary[PdfDictionaryProperties.subtype]); + if (subtype != null && subtype is PdfName) { + if (subtype.name == 'FileAttachment') { + final PdfDictionary fileDictionary = PdfDictionary(); + fileDictionary.setName(PdfDictionaryProperties.type, + PdfDictionaryProperties.filespec); + _addElementStrings(fileDictionary, element, 'file', + PdfDictionaryProperties.f); + _addElementStrings(fileDictionary, element, 'file', + PdfDictionaryProperties.uf); + final PdfStream fileStream = PdfStream(); + final PdfDictionary param = PdfDictionary(); + final String? sizeAttributes = + element.getAttribute('size'); + if (!isNullOrEmpty(sizeAttributes)) { + final int? size = int.tryParse(sizeAttributes!); + if (size != null) { + param.setNumber(PdfDictionaryProperties.size, size); + fileStream.setNumber('DL', size); + } + } + _addElementStrings(param, element, 'modification', + PdfDictionaryProperties.modificationDate); + _addElementStrings(param, element, 'creation', + PdfDictionaryProperties.creationDate); + fileStream.setProperty( + PdfDictionaryProperties.params, param); + _addElementStrings(fileStream, element, 'mimetype', + PdfDictionaryProperties.subtype); + fileStream.dataStream = raw; + fileStream + .addFilter(PdfDictionaryProperties.flateDecode); + final PdfDictionary embeddedFile = PdfDictionary(); + embeddedFile.setProperty(PdfDictionaryProperties.f, + PdfReferenceHolder(fileStream)); + fileDictionary.setProperty( + PdfDictionaryProperties.ef, embeddedFile); + annotDictionary.setProperty(PdfDictionaryProperties.fs, + PdfReferenceHolder(fileDictionary)); + } else if (subtype.name == 'Sound') { + final PdfStream soundStream = PdfStream(); + soundStream.setName( + PdfDictionaryProperties.type, 'Sound'); + _addNumber(soundStream, element, 'bits', 'B'); + _addNumber(soundStream, element, 'rate', + PdfDictionaryProperties.r); + _addNumber(soundStream, element, 'channels', + PdfDictionaryProperties.c); + String? attribute = element.getAttribute('encoding'); + if (!isNullOrEmpty(attribute)) { + soundStream.setName( + PdfDictionaryProperties.e, attribute); + } + soundStream.dataStream = raw; + attribute = element.getAttribute('filter'); + if (!isNullOrEmpty(attribute)) { + soundStream + .addFilter(PdfDictionaryProperties.flateDecode); + } + annotDictionary.setProperty( + 'Sound', PdfReferenceHolder(soundStream)); + } + } + } + } + } + break; + } + } + } + } + } + + void _addElementStrings(PdfDictionary dictionary, XmlElement element, + String attributeName, String key) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(attributeName); + if (!isNullOrEmpty(attribute)) { + _addString(dictionary, key, attribute); + } + } + } + + void _addNumber(PdfDictionary dictionary, XmlElement element, + String attributeName, String key) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(attributeName); + if (!isNullOrEmpty(attribute)) { + _addInt(dictionary, key, attribute); + } + } + } + + void _addImageToAppearance(PdfDictionary annotDictionary, String value) { + final String convert = value + .replaceAll('data:image/png;base64,', '') + .replaceAll('data:image/jpg;base64,', '') + .replaceAll('data:image/bmp;base64,', ''); + final List appearanceArray = base64.decode(convert); + final PdfBitmap image = PdfBitmap(appearanceArray); + final IPdfPrimitive? array = PdfCrossTable.dereference( + annotDictionary[PdfDictionaryProperties.rect]); + if (array != null && array is PdfArray) { + const double x = 0; + const double y = 0; + final double width = (array[2]! as PdfNumber).value!.toDouble(); + final double height = (array[3]! as PdfNumber).value!.toDouble(); + final Rect rect = Rect.fromLTWH(x, y, width, height); + final PdfTemplate template = PdfTemplate(width, height); + _setMatrix( + PdfTemplateHelper.getHelper(template).content, annotDictionary); + template.graphics!.drawImage(image, rect); + final PdfReferenceHolder refHolder = PdfReferenceHolder(template); + final PdfDictionary appearanceDictionary = PdfDictionary(); + appearanceDictionary.setProperty(PdfDictionaryProperties.n, refHolder); + annotDictionary.setProperty( + PdfDictionaryProperties.ap, appearanceDictionary); + } + } + + void _setMatrix(PdfDictionary template, PdfDictionary annotDictionary) { + final IPdfPrimitive? bbox = + PdfCrossTable.dereference(template[PdfDictionaryProperties.bBox]); + if (bbox != null && bbox is PdfArray) { + if (annotDictionary.containsKey(PdfDictionaryProperties.rotate)) { + final IPdfPrimitive? rotate = PdfCrossTable.dereference( + annotDictionary[PdfDictionaryProperties.rotate]); + if (rotate is PdfNumber && rotate.value != 0) { + int rotateAngle = rotate.value!.toInt(); + if (rotateAngle == 0) { + rotateAngle = rotateAngle * 90; + } + final PdfTransformationMatrix matrix = PdfTransformationMatrix(); + matrix.rotate(rotateAngle.toDouble()); + final PdfArray mMatrix = PdfArray(matrix.matrix.elements); + template[PdfDictionaryProperties.matrix] = mMatrix; + } + } else { + final List elements = [ + 1, + 0, + 0, + 1, + -(bbox[0]! as PdfNumber).value!, + -(bbox[1]! as PdfNumber).value! + ]; + template[PdfDictionaryProperties.matrix] = PdfArray(elements); + } + } + } + + PdfDictionary _getAppearance(PdfDictionary appearance, XmlElement element) { + final XmlName elementName = element.name; + switch (elementName.local) { + case XfdfProperties.stream: + final PdfStream stream = _getStream(element); + final PdfReferenceHolder holder = PdfReferenceHolder(stream); + _addKey(holder, appearance, element); + break; + case XfdfProperties.dict: + final PdfDictionary dictionary = _getDictionary(element); + final PdfReferenceHolder holder = PdfReferenceHolder(dictionary); + _addKey(holder, appearance, element); + break; + case XfdfProperties.array: + final PdfArray array = _getArray(element); + _addKey(array, appearance, element); + break; + case XfdfProperties.fixed: + final PdfNumber? floatNumber = _getFixed(element); + _addKey(floatNumber, appearance, element); + break; + case XfdfProperties.int: + final PdfNumber? intNumber = _getInt(element); + _addKey(intNumber, appearance, element); + break; + case XfdfProperties.string: + final PdfString? mstring = _getString(element); + _addKey(mstring, appearance, element); + break; + case XfdfProperties.name: + final PdfName? name = _getName(element); + _addKey(name, appearance, element); + break; + case XfdfProperties.bool: + final PdfBoolean? boolean = _getBoolean(element); + _addKey(boolean, appearance, element); + break; + case XfdfProperties.data: + final List? data = _getData(element); + if (data != null && data.isNotEmpty) { + if (appearance is PdfStream) { + appearance.dataStream = data; + if (!appearance.containsKey(PdfDictionaryProperties.type) && + !appearance.containsKey(PdfDictionaryProperties.subtype)) { + appearance.decompress(); + } + bool isImage = false; + if (appearance.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? subtype = PdfCrossTable.dereference( + appearance[PdfDictionaryProperties.subtype]); + if (subtype != null && + subtype is PdfName && + subtype.name == PdfDictionaryProperties.image) { + isImage = true; + } + } + if (isImage) { + appearance.compress = false; + } else { + if (appearance.containsKey(PdfDictionaryProperties.length)) { + appearance.remove(PdfDictionaryProperties.length); + } + if (appearance.containsKey(PdfDictionaryProperties.filter)) { + appearance.remove(PdfDictionaryProperties.filter); + } + } + } + } + break; + } + + return appearance; + } + + void _addMeasureDictionary( + PdfDictionary annotDictionary, XmlElement element) { + XmlElement? measurement; + XmlElement? area; + XmlElement? distance; + XmlElement? xformat; + for (final XmlNode childNode in element.children) { + if (childNode is XmlElement) { + final XmlName childName = childNode.name; + if (childName.local.toLowerCase() == 'measure') { + measurement = childNode; + break; + } + } + } + final PdfDictionary measureDictionary = PdfDictionary(); + final PdfArray dArray = PdfArray(); + final PdfArray aArray = PdfArray(); + final PdfArray xArray = PdfArray(); + final PdfDictionary dDict = PdfDictionary(); + final PdfDictionary aDict = PdfDictionary(); + final PdfDictionary xDict = PdfDictionary(); + measureDictionary.items![PdfName(PdfDictionaryProperties.a)] = aArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.d)] = dArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.x)] = xArray; + if (measurement != null) { + measureDictionary.setName( + PdfDictionaryProperties.type, PdfDictionaryProperties.measure); + if (measurement.attributes.isNotEmpty) { + final String? attribute = measurement.getAttribute('rateValue'); + if (!isNullOrEmpty(attribute)) { + measureDictionary.setString(PdfDictionaryProperties.r, attribute); + } + } + for (final XmlNode childNode in measurement.children) { + if (childNode is XmlElement) { + final XmlName newElement = childNode.name; + if (newElement.local.toLowerCase() == 'area') { + area = childNode; + } + if (newElement.local.toLowerCase() == 'distance') { + distance = childNode; + } + if (newElement.local.toLowerCase() == 'xformat') { + xformat = childNode; + } + } + } + } + if (xformat != null) { + _addElements(xformat, xDict); + xArray.add(xDict); + } + if (area != null) { + _addElements(area, aDict); + aArray.add(aDict); + } + if (distance != null) { + _addElements(distance, dDict); + dArray.add(dDict); + } + if (measureDictionary.items!.isNotEmpty && + measureDictionary.containsKey(PdfDictionaryProperties.type)) { + annotDictionary.items![PdfName(PdfDictionaryProperties.measure)] = + PdfReferenceHolder(measureDictionary); + } + } + + void _addElements(XmlElement element, PdfDictionary dictionary) { + if (element.attributes.isNotEmpty) { + String? attribute = element.getAttribute('d'); + if (attribute != null) { + final double? d = double.tryParse(attribute); + if (d != null) { + dictionary.setProperty( + PdfName(PdfDictionaryProperties.d), PdfNumber(d)); + } + } + attribute = element.getAttribute(PdfDictionaryProperties.c.toLowerCase()); + if (attribute != null) { + final double? c = double.tryParse(attribute); + if (c != null) { + dictionary.setProperty( + PdfName(PdfDictionaryProperties.c), PdfNumber(c)); + } + } + attribute = element.getAttribute('rt'); + if (attribute != null) { + dictionary.items![PdfName('RT')] = PdfString(attribute); + } + attribute = element.getAttribute('rd'); + if (attribute != null) { + dictionary.items![PdfName(PdfDictionaryProperties.rd)] = + PdfString(attribute); + } + attribute = element.getAttribute('ss'); + if (attribute != null) { + dictionary.items![PdfName('SS')] = PdfString(attribute); + } + attribute = element.getAttribute(PdfDictionaryProperties.u.toLowerCase()); + if (attribute != null) { + dictionary.items![PdfName(PdfDictionaryProperties.u)] = + PdfString(attribute); + } + attribute = element.getAttribute(PdfDictionaryProperties.f.toLowerCase()); + if (attribute != null) { + dictionary.items![PdfName(PdfDictionaryProperties.f)] = + PdfName(attribute); + } + attribute = element.getAttribute('fd'); + if (attribute != null) { + dictionary.items![PdfName('FD')] = PdfBoolean(attribute == 'yes'); + } + } + } + + PdfStream _getStream(XmlElement element) { + final PdfStream stream = PdfStream(); + if (element.children.isNotEmpty) { + for (final XmlNode child in element.children) { + if (child is XmlElement) { + _getAppearance(stream, child); + } + } + } + return stream; + } + + void _addKey( + IPdfPrimitive? primitive, PdfDictionary dictionary, XmlElement element) { + if (primitive != null && element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.key); + if (!isNullOrEmpty(attribute)) { + dictionary.setProperty(attribute, primitive); + } + } + } + + PdfDictionary _getDictionary(XmlElement element) { + final PdfDictionary dictionary = PdfDictionary(); + if (element.children.isNotEmpty) { + for (final XmlNode child in element.children) { + if (child is XmlElement) { + _getAppearance(dictionary, child); + } + } + } + return dictionary; + } + + PdfArray _getArray(XmlElement element) { + final PdfArray array = PdfArray(); + if (element.children.isNotEmpty) { + for (final XmlNode child in element.children) { + if (child is XmlElement) { + _addArrayElements(array, child); + } + } + } + return array; + } + + PdfNumber? _getFixed(XmlElement element) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.val); + if (!isNullOrEmpty(attribute)) { + final double? value = double.tryParse(attribute!); + if (value != null) { + return PdfNumber(value); + } + } + } + return null; + } + + PdfNumber? _getInt(XmlElement element) { + // int value; + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.val); + if (!isNullOrEmpty(attribute)) { + final int? value = int.tryParse(attribute!); + if (value != null) { + return PdfNumber(value); + } + } + } + return null; + } + + void _addArrayElements(PdfArray array, XmlElement element) { + final XmlName elementName = element.name; + switch (elementName.local) { + case XfdfProperties.stream: + final PdfStream stream = _getStream(element); + _addArrayElement(array, PdfReferenceHolder(stream)); + break; + case XfdfProperties.dict: + final PdfDictionary dictionary = _getDictionary(element); + _addArrayElement(array, PdfReferenceHolder(dictionary)); + break; + case XfdfProperties.array: + final PdfArray pdfArray = _getArray(element); + _addArrayElement(array, pdfArray); + break; + case XfdfProperties.fixed: + final PdfNumber? floatValue = _getFixed(element); + _addArrayElement(array, floatValue); + break; + case XfdfProperties.int: + final PdfNumber? intValue = _getInt(element); + _addArrayElement(array, intValue); + break; + case XfdfProperties.name: + final PdfName? name = _getName(element); + _addArrayElement(array, name); + break; + case XfdfProperties.bool: + final PdfBoolean? boolean = _getBoolean(element); + _addArrayElement(array, boolean); + break; + case XfdfProperties.string: + final PdfString? mstring = _getString(element); + _addArrayElement(array, mstring); + break; + } + } + + void _addArrayElement(PdfArray array, IPdfPrimitive? primitive) { + if (primitive != null) { + array.add(primitive); + } + } + + PdfName? _getName(XmlElement element) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.val); + if (!isNullOrEmpty(attribute)) { + return PdfName(attribute); + } + } + return null; + } + + PdfBoolean? _getBoolean(XmlElement element) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.val); + if (!isNullOrEmpty(attribute)) { + return PdfBoolean(attribute!.toLowerCase() == 'true'); + } + } + return null; + } + + PdfString? _getString(XmlElement element) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.val); + if (!isNullOrEmpty(attribute)) { + return PdfString(attribute!); + } else if (element.name.toXmlString().contains(XfdfProperties.string)) { + final List? data = _getData(element); + if (data != null && data.isNotEmpty) { + return PdfString.fromBytes(data); + } + } + } + return null; + } + + List? _getData(XmlElement element) { + if (!isNullOrEmpty(element.innerText) && element.attributes.isNotEmpty) { + final String? mode = element.getAttribute(XfdfProperties.mode); + final String? encoding = element.getAttribute('ENCODING'); + if (!isNullOrEmpty(mode) && !isNullOrEmpty(encoding)) { + if (mode == XfdfProperties.filtered && + encoding == XfdfProperties.ascii) { + return [...utf8.encode(element.innerText)]; + } else if (mode == XfdfProperties.raw && + encoding == XfdfProperties.hex) { + return _hexToBytes(element.innerText); + } + } else if (!isNullOrEmpty(encoding) && encoding == XfdfProperties.hex) { + return _hexToBytes(element.innerText); + } + } + return null; + } + + List _hexToBytes(String hexString) { + final List bytes = []; + for (int i = 0; i < hexString.length; i += 2) { + final String hex = hexString.substring(i, i + 2); + bytes.add(int.parse(hex, radix: 16)); + } + return bytes; + } + + void _addReferenceToGroup( + PdfReferenceHolder holder, PdfDictionary dictionary) { + IPdfPrimitive? name = PdfCrossTable.dereference(dictionary['NM']); + if (name != null && name is PdfString && !isNullOrEmpty(name.value)) { + _groupReferences ??= {}; + _groupReferences![name.value!] = holder; + if (dictionary.containsKey(PdfDictionaryProperties.irt)) { + _groupHolders ??= []; + _groupHolders!.add(dictionary); + } + } else if (name == null) { + if (dictionary.containsKey(PdfDictionaryProperties.irt)) { + name = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.irt]); + } + if (name != null && name is PdfString && !isNullOrEmpty(name.value)) { + if (_groupReferences != null && + _groupReferences!.containsKey(name.value)) { + final PdfReferenceHolder referenceHolder = + _groupReferences![name.value]!; + dictionary[PdfDictionaryProperties.irt] = referenceHolder; + } + } + } + } + + String? _getFormattedString(String? value) { + if (!isNullOrEmpty(value)) { + value = value!.replaceAll('<', '<'); + value = value.replaceAll('>', '>'); + } + return value; + } + + void _addString(PdfDictionary dictionary, String key, String? value) { + if (!isNullOrEmpty(value)) { + dictionary.setString(key, value); + } + } + + void _addInt(PdfDictionary dictionary, String key, String? value) { + if (!isNullOrEmpty(value)) { + final int? number = int.tryParse(value!); + if (number != null) { + dictionary.setNumber(key, number); + } + } + } + + void _addFloatPoints(PdfDictionary dictionary, List points, String key) { + if (points.isNotEmpty) { + dictionary.setProperty(key, PdfArray(points)); + } + } + + void _addFloat(PdfDictionary dictionary, String key, String value) { + if (!isNullOrEmpty(value)) { + final int? number = int.tryParse(value); + if (number != null) { + dictionary.setNumber(key, number); + } + } + } + + PdfArray? _getColorArray(String value) { + final String hex = value.replaceAll('#', ''); + final int r = int.parse(hex.substring(0, 2), radix: 16); + final int g = int.parse(hex.substring(2, 4), radix: 16); + final int b = int.parse(hex.substring(4, hex.length), radix: 16); + if (r >= 0 && g >= 0 && b >= 0) { + final PdfArray colorArray = PdfArray(); + colorArray.add(PdfNumber(r / 255)); + colorArray.add(PdfNumber(g / 255)); + colorArray.add(PdfNumber(b / 255)); + return colorArray; + } + return null; + } + + ///Internal method + static PdfAnnotationFlags mapAnnotationFlags(String flag) { + PdfAnnotationFlags type; + switch (flag.toLowerCase()) { + case 'hidden': + type = PdfAnnotationFlags.hidden; + break; + case 'invisible': + type = PdfAnnotationFlags.invisible; + break; + case 'locked': + type = PdfAnnotationFlags.locked; + break; + case 'norotate': + type = PdfAnnotationFlags.noRotate; + break; + case 'noview': + type = PdfAnnotationFlags.noView; + break; + case 'nozoom': + type = PdfAnnotationFlags.noZoom; + break; + case 'print': + type = PdfAnnotationFlags.print; + break; + case 'readonly': + type = PdfAnnotationFlags.readOnly; + break; + case 'togglenoview': + type = PdfAnnotationFlags.toggleNoView; + break; + default: + type = PdfAnnotationFlags.defaultFlag; + break; + } + return type; + } + + void _addLineEndStyle(XmlElement element, PdfDictionary annotDictionary) { + if (element.attributes.isNotEmpty) { + String beginLineStyle = ''; + final String? headAttribute = element.getAttribute('head'); + if (!isNullOrEmpty(headAttribute)) { + beginLineStyle = getEnumName(mapLineEndingStyle(headAttribute!)); + } + String endLineStyle = ''; + final String? tailAttribute = element.getAttribute('tail'); + if (!isNullOrEmpty(tailAttribute)) { + endLineStyle = getEnumName(mapLineEndingStyle(tailAttribute!)); + } + if (!isNullOrEmpty(beginLineStyle)) { + if (!isNullOrEmpty(endLineStyle)) { + final PdfArray lineEndingStyles = PdfArray(); + lineEndingStyles.add(PdfName(beginLineStyle)); + lineEndingStyles.add(PdfName(endLineStyle)); + annotDictionary.setProperty( + PdfDictionaryProperties.le, lineEndingStyles); + } else { + annotDictionary.setName(PdfDictionaryProperties.le, beginLineStyle); + } + } else if (!isNullOrEmpty(endLineStyle)) { + annotDictionary.setName(PdfDictionaryProperties.le, beginLineStyle); + } + } + } + + /// Internal Field. + static PdfLineEndingStyle mapLineEndingStyle(String style) { + PdfLineEndingStyle lineStyle; + switch (style.toLowerCase()) { + case 'butt': + lineStyle = PdfLineEndingStyle.butt; + break; + case 'circle': + lineStyle = PdfLineEndingStyle.circle; + break; + case 'closedarrow': + lineStyle = PdfLineEndingStyle.closedArrow; + break; + case 'diamond': + lineStyle = PdfLineEndingStyle.diamond; + break; + case 'openarrow': + lineStyle = PdfLineEndingStyle.openArrow; + break; + case 'rclosedarrow': + lineStyle = PdfLineEndingStyle.rClosedArrow; + break; + case 'ropenarrow': + lineStyle = PdfLineEndingStyle.rOpenArrow; + break; + case 'slash': + lineStyle = PdfLineEndingStyle.slash; + break; + case 'square': + lineStyle = PdfLineEndingStyle.square; + break; + default: + lineStyle = PdfLineEndingStyle.none; + break; + } + return lineStyle; + } +} From 783b55f95a45d8518d781b467fd9d0d29f7d9af9 Mon Sep 17 00:00:00 2001 From: LokeshPalani Date: Wed, 20 Mar 2024 16:12:53 +0530 Subject: [PATCH 08/11] Added missed file theme in the pdfviewer --- .../lib/src/theme/theme.dart | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 packages/syncfusion_flutter_pdfviewer/lib/src/theme/theme.dart diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/theme/theme.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/theme/theme.dart new file mode 100644 index 000000000..447b8a624 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/theme/theme.dart @@ -0,0 +1,181 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// Holds the default values if Material 3 is not enabled. +class SfPdfViewerThemeDataM2 extends SfPdfViewerThemeData { + /// Creates a [SfPdfViewerThemeDataM2] that holds the default values + /// for [SfPdfViewer] when Material 3 is not enabled. + SfPdfViewerThemeDataM2(this.context); + + /// The [BuildContext] of the widget. + final BuildContext context; + + /// The [ColorScheme] of the widget. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + late final PdfScrollHeadStyle _scrollHeadStyle = PdfScrollHeadStyle( + backgroundColor: colorScheme.brightness == Brightness.light + ? const Color(0xFFFAFAFA) + : const Color(0xFF424242), + ); + + late final PdfBookmarkViewStyle _bookmarkViewStyle = PdfBookmarkViewStyle( + backgroundColor: colorScheme.brightness == Brightness.light + ? Colors.white + : const Color(0xFF212121), + closeIconColor: colorScheme.brightness == Brightness.light + ? Colors.black.withOpacity(0.54) + : Colors.white.withOpacity(0.54), + backIconColor: colorScheme.brightness == Brightness.light + ? Colors.black.withOpacity(0.54) + : Colors.white.withOpacity(0.54), + headerBarColor: colorScheme.brightness == Brightness.light + ? const Color(0xFFFAFAFA) + : const Color(0xFF424242), + navigationIconColor: colorScheme.brightness == Brightness.light + ? Colors.black.withOpacity(0.54) + : Colors.white.withOpacity(0.54), + selectionColor: colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 0.08) + : const Color.fromRGBO(255, 255, 255, 0.12), + titleSeparatorColor: colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 0.16) + : const Color.fromRGBO(255, 255, 255, 0.16), + ); + + late final PdfPaginationDialogStyle _paginationDialogStyle = + PdfPaginationDialogStyle( + backgroundColor: colorScheme.brightness == Brightness.light + ? Colors.white + : const Color(0xFF424242), + ); + + late final PdfHyperlinkDialogStyle _hyperlinkDialogStyle = + PdfHyperlinkDialogStyle( + backgroundColor: colorScheme.brightness == Brightness.light + ? Colors.white + : const Color(0xFF424242), + closeIconColor: colorScheme.brightness == Brightness.light + ? Colors.black.withOpacity(0.6) + : Colors.white.withOpacity(0.6), + ); + + late final PdfPasswordDialogStyle _passwordDialogStyle = + PdfPasswordDialogStyle( + backgroundColor: colorScheme.brightness == Brightness.light + ? Colors.white + : const Color(0xFF424242), + closeIconColor: colorScheme.brightness == Brightness.light + ? Colors.black.withOpacity(0.6) + : Colors.white.withOpacity(0.6), + visibleIconColor: colorScheme.brightness == Brightness.light + ? Colors.black.withOpacity(0.6) + : Colors.white.withOpacity(0.6), + ); + + @override + Color? get backgroundColor => colorScheme.brightness == Brightness.light + ? const Color(0xFFD6D6D6) + : const Color(0xFF303030); + + @override + PdfScrollHeadStyle get scrollHeadStyle => _scrollHeadStyle; + @override + PdfBookmarkViewStyle get bookmarkViewStyle => _bookmarkViewStyle; + + @override + PdfPaginationDialogStyle get paginationDialogStyle => _paginationDialogStyle; + + @override + PdfHyperlinkDialogStyle get hyperlinkDialogStyle => _hyperlinkDialogStyle; + + @override + PdfPasswordDialogStyle get passwordDialogStyle => _passwordDialogStyle; +} + +/// Holds the default values if Material 3 is enabled. +class SfPdfViewerThemeDataM3 extends SfPdfViewerThemeData { + /// Creates a [SfPdfViewerThemeDataM3] that holds the default values + /// for [SfPdfViewer] when Material 3 is enabled. + SfPdfViewerThemeDataM3(this.context); + + /// The [BuildContext] of the widget. + final BuildContext context; + + /// The [ColorScheme] of the widget. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + late final PdfScrollHeadStyle _scrollHeadStyle = PdfScrollHeadStyle( + backgroundColor: colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(247, 242, 251, 1) + : const Color.fromRGBO(37, 35, 42, 1), + ); + + late final PdfScrollStatusStyle _scrollStatusStyle = PdfScrollStatusStyle( + backgroundColor: colorScheme.inverseSurface, + pageInfoTextStyle: TextStyle(color: colorScheme.onInverseSurface)); + + late final PdfBookmarkViewStyle _bookmarkViewStyle = PdfBookmarkViewStyle( + backgroundColor: colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(247, 242, 251, 1) + : const Color.fromRGBO(37, 35, 42, 1), + closeIconColor: colorScheme.onSurfaceVariant, + backIconColor: colorScheme.onSurfaceVariant, + headerBarColor: colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(247, 242, 251, 1) + : const Color.fromRGBO(37, 35, 42, 1), + navigationIconColor: colorScheme.onSurfaceVariant, + selectionColor: colorScheme.primaryContainer, + titleSeparatorColor: colorScheme.outlineVariant, + ); + + late final PdfPaginationDialogStyle _paginationDialogStyle = + PdfPaginationDialogStyle( + backgroundColor: colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1), + ); + + late final PdfHyperlinkDialogStyle _hyperlinkDialogStyle = + PdfHyperlinkDialogStyle( + backgroundColor: colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1), + closeIconColor: colorScheme.onSurfaceVariant, + ); + + late final PdfPasswordDialogStyle _passwordDialogStyle = + PdfPasswordDialogStyle( + backgroundColor: colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1), + closeIconColor: colorScheme.onSurfaceVariant, + visibleIconColor: colorScheme.onSurfaceVariant, + ); + + @override + Color? get backgroundColor => colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(237, 230, 243, 1) + : const Color.fromRGBO(50, 46, 58, 1); + + @override + Color? get progressBarColor => colorScheme.primary; + + @override + PdfScrollHeadStyle get scrollHeadStyle => _scrollHeadStyle; + + @override + PdfScrollStatusStyle get scrollStatusStyle => _scrollStatusStyle; + + @override + PdfBookmarkViewStyle get bookmarkViewStyle => _bookmarkViewStyle; + + @override + PdfPaginationDialogStyle get paginationDialogStyle => _paginationDialogStyle; + + @override + PdfHyperlinkDialogStyle get hyperlinkDialogStyle => _hyperlinkDialogStyle; + + @override + PdfPasswordDialogStyle get passwordDialogStyle => _passwordDialogStyle; +} From 4952485ed9d6e6d203a3dc0e34e2495ea73c1b00 Mon Sep 17 00:00:00 2001 From: LokeshPalani Date: Wed, 20 Mar 2024 16:19:44 +0530 Subject: [PATCH 09/11] Added the missed files in the pdfviewer platform interface --- .../example/lib/main.dart | 53 ++++++++++++++ .../lib/pdfviewer_platform_interface.dart | 1 + .../lib/src/method_channel_pdfviewer.dart | 62 +++++++++++++++++ .../lib/src/pdfviewer_platform_interface.dart | 69 +++++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 packages/syncfusion_flutter_pdfviewer_platform_interface/example/lib/main.dart create mode 100644 packages/syncfusion_flutter_pdfviewer_platform_interface/lib/pdfviewer_platform_interface.dart create mode 100644 packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart create mode 100644 packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/lib/main.dart b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/lib/main.dart new file mode 100644 index 000000000..c476c0306 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/lib/main.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; + +void main() { + runApp(MaterialApp( + title: 'Syncfusion PDF Viewer Demo for Web', + theme: ThemeData( + useMaterial3: false, + ), + home: const HomePage(), + )); +} + +/// Represents Homepage for Navigation +class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + + @override + _HomePage createState() => _HomePage(); +} + +class _HomePage extends State { + final GlobalKey _pdfViewerKey = GlobalKey(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Syncfusion Flutter PDF Viewer'), + actions: [ + IconButton( + icon: const Icon( + Icons.bookmark, + color: Colors.white, + ), + onPressed: () { + _pdfViewerKey.currentState?.openBookmarkView(); + }, + ), + ], + ), + body: SfPdfViewer.network( + 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + key: _pdfViewerKey, + ), + ); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/pdfviewer_platform_interface.dart b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/pdfviewer_platform_interface.dart new file mode 100644 index 000000000..81a29938d --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/pdfviewer_platform_interface.dart @@ -0,0 +1 @@ +export 'src/pdfviewer_platform_interface.dart'; diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart new file mode 100644 index 000000000..bd71c47ee --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart @@ -0,0 +1,62 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; +import 'package:syncfusion_pdfviewer_platform_interface/pdfviewer_platform_interface.dart'; + +class MethodChannelPdfViewer extends PdfViewerPlatform { + final MethodChannel _channel = MethodChannel('syncfusion_flutter_pdfviewer'); + + /// Initializes the PDF renderer instance in respective platform by loading the PDF from the provided byte information. + /// If success, returns page count else returns error message from respective platform + @override + Future initializePdfRenderer( + Uint8List documentBytes, String documentID) async { + return _channel.invokeMethod('initializePdfRenderer', { + 'documentBytes': documentBytes, + 'documentID': documentID + }); + } + + /// Gets the height of all pages in the document. + @override + Future getPagesHeight(String documentID) async { + return _channel.invokeMethod('getPagesHeight', documentID); + } + + /// Gets the width of all pages in the document. + @override + Future getPagesWidth(String documentID) async { + return _channel.invokeMethod('getPagesWidth', documentID); + } + + /// Gets the image's bytes information of the specified page. + @override + Future getImage( + int pageNumber, double currentScale, String documentID) async { + return _channel.invokeMethod('getImage', { + 'index': pageNumber, + 'scale': currentScale, + 'documentID': documentID + }); + } + + /// Gets the image's bytes information of the specified portion of the page + @override + Future getTileImage(int pageNumber, double currentScale, double x, + double y, double width, double height, String documentID) async { + return _channel.invokeMethod('getTileImage', { + 'pageNumber': pageNumber, + 'scale': currentScale, + 'x': x, + 'y': y, + 'width': width, + 'height': height, + 'documentID': documentID + }); + } + + /// Closes the PDF document. + @override + Future closeDocument(String documentID) async { + return _channel.invokeMethod('closeDocument', documentID); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart new file mode 100644 index 000000000..04a285611 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart @@ -0,0 +1,69 @@ +library syncfusion_pdfviewer_platform_interface; + +import 'dart:async'; +import 'dart:typed_data'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:syncfusion_pdfviewer_platform_interface/src/method_channel_pdfviewer.dart'; + +/// The interface that implementations of syncfusion_flutter_pdfviewer must implement. +/// +/// Platform implementations should extend this class rather than implement it as `syncfusion_flutter_pdfviewer` +/// does not consider newly added methods to be breaking changes. Extending this class +/// (using `extends`) ensures that the subclass will get the default implementation, while +/// platform implementations that `implements` this interface will be broken by newly added +/// [PdfViewerPlatform] methods +abstract class PdfViewerPlatform extends PlatformInterface { + /// Constructs a PdfViewerPlatform. + PdfViewerPlatform() : super(token: _token); + static PdfViewerPlatform _instance = MethodChannelPdfViewer(); + + static final Object _token = Object(); + + /// The default instance of [PdfViewerPlatform] to use. + /// + /// Defaults to [MethodChannelPdfViewer] + static PdfViewerPlatform get instance => _instance; + + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [PdfViewerPlatform] when they register themselves. + static set instance(PdfViewerPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// Initializes the PDF renderer instance in respective platform by loading the PDF from the specified path. + /// + /// If success, returns page count else returns error message from respective platform + Future initializePdfRenderer( + Uint8List documentBytes, String documentID) async { + throw UnimplementedError( + 'initializePdfRenderer() has not been implemented.'); + } + + /// Gets the height of all pages in the document. + Future getPagesHeight(String documentID) async { + throw UnimplementedError('getPagesHeight() has not been implemented.'); + } + + /// Gets the width of all pages in the document. + Future getPagesWidth(String documentID) async { + throw UnimplementedError('getPagesWidth() has not been implemented.'); + } + + /// Gets the image's bytes information of the specified page. + Future getImage( + int pageNumber, double scale, String documentID) async { + throw UnimplementedError('getImage() has not been implemented.'); + } + + /// Gets the image's bytes information of the specified portion of the page. + Future getTileImage(int pageNumber, double scale, double x, + double y, double width, double height, String documentID) async { + throw UnimplementedError('getTileImage() has not been implemented.'); + } + + /// Closes the PDF document. + Future closeDocument(String documentID) async { + throw UnimplementedError('closeDocument() has not been implemented.'); + } +} From 19cfdd13e53935f26822bda99fb14fe6ddf83bd4 Mon Sep 17 00:00:00 2001 From: LokeshPalani Date: Wed, 20 Mar 2024 16:33:34 +0530 Subject: [PATCH 10/11] Add the missed files in the office chart --- .../example/lib/helper/save_file_mobile.dart | 37 + .../example/lib/helper/save_file_web.dart | 15 + .../example/lib/main.dart | 179 + .../lib/officechart.dart | 28 + .../lib/src/chart/chart_axis.dart | 80 + .../lib/src/chart/chart_category_axis.dart | 32 + .../lib/src/chart/chart_collection.dart | 108 + .../lib/src/chart/chart_datalabel.dart | 41 + .../lib/src/chart/chart_enum.dart | 199 ++ .../lib/src/chart/chart_fomat_impl.dart | 129 + .../lib/src/chart/chart_format.dart | 21 + .../lib/src/chart/chart_impl.dart | 1156 ++++++ .../lib/src/chart/chart_legend.dart | 34 + .../lib/src/chart/chart_plotarea.dart | 24 + .../lib/src/chart/chart_serialization.dart | 3162 +++++++++++++++++ .../lib/src/chart/chart_serie.dart | 92 + .../lib/src/chart/chart_serie_dataformat.dart | 19 + .../src/chart/chart_series_collection.dart | 50 + .../lib/src/chart/chart_text_area.dart | 94 + .../lib/src/chart/chart_value_axis.dart | 18 + .../src/chart/chartserie_dataformat_impl.dart | 92 + 21 files changed, 5610 insertions(+) create mode 100644 packages/syncfusion_flutter_officechart/example/lib/helper/save_file_mobile.dart create mode 100644 packages/syncfusion_flutter_officechart/example/lib/helper/save_file_web.dart create mode 100644 packages/syncfusion_flutter_officechart/example/lib/main.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/officechart.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_axis.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_category_axis.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_collection.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_datalabel.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_enum.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_fomat_impl.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_format.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_impl.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_legend.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_plotarea.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_serialization.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie_dataformat.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_series_collection.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_text_area.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chart_value_axis.dart create mode 100644 packages/syncfusion_flutter_officechart/lib/src/chart/chartserie_dataformat_impl.dart diff --git a/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_mobile.dart new file mode 100644 index 000000000..c7ddbff8b --- /dev/null +++ b/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_mobile.dart @@ -0,0 +1,37 @@ +import 'dart:io'; + +import 'package:open_file/open_file.dart' as open_file; +import 'package:path_provider/path_provider.dart' as path_provider; +// ignore: depend_on_referenced_packages +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; + +///To save the Excel file in the device +///To save the Excel file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + //Get the storage folder location using path_provider package. + String? path; + if (Platform.isAndroid || + Platform.isIOS || + Platform.isLinux || + Platform.isWindows) { + final Directory directory = + await path_provider.getApplicationSupportDirectory(); + path = directory.path; + } else { + path = await PathProviderPlatform.instance.getApplicationSupportPath(); + } + final File file = + File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); + await file.writeAsBytes(bytes, flush: true); + if (Platform.isAndroid || Platform.isIOS) { + //Launch the file (used open_file package) + await open_file.OpenFile.open('$path/$fileName'); + } else if (Platform.isWindows) { + await Process.run('start', ['$path\\$fileName'], runInShell: true); + } else if (Platform.isMacOS) { + await Process.run('open', ['$path/$fileName'], runInShell: true); + } else if (Platform.isLinux) { + await Process.run('xdg-open', ['$path/$fileName'], + runInShell: true); + } +} diff --git a/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_web.dart b/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_web.dart new file mode 100644 index 000000000..a455ce682 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_web.dart @@ -0,0 +1,15 @@ +///Dart imports +import 'dart:async'; +import 'dart:convert'; +// ignore: avoid_web_libraries_in_flutter +import 'dart:html'; + +///To save the Excel file in the device +///To save the Excel file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + AnchorElement( + href: + 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') + ..setAttribute('download', fileName) + ..click(); +} diff --git a/packages/syncfusion_flutter_officechart/example/lib/main.dart b/packages/syncfusion_flutter_officechart/example/lib/main.dart new file mode 100644 index 000000000..58767f751 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/example/lib/main.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +// ignore: depend_on_referenced_packages +import 'package:syncfusion_flutter_xlsio/xlsio.dart' hide Column; +import 'package:syncfusion_officechart/officechart.dart'; + +//Local imports +import 'helper/save_file_mobile.dart' + if (dart.library.html) 'helper/save_file_web.dart'; + +void main() { + runApp(CreateOfficeChartWidget()); +} + +/// Represents the office chart widget class. +class CreateOfficeChartWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: CreateOfficeChartStatefulWidget(title: 'Create Excel document'), + ); + } +} + +/// Represents the office chart stateful widget class. +class CreateOfficeChartStatefulWidget extends StatefulWidget { + /// Initalize the instance of the [CreateOfficeChartStatefulWidget] class. + const CreateOfficeChartStatefulWidget({Key? key, required this.title}) + : super(key: key); + + /// title. + final String title; + @override + // ignore: library_private_types_in_public_api + _CreateOfficeChartState createState() => _CreateOfficeChartState(); +} + +class _CreateOfficeChartState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + style: TextButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.lightBlue, + disabledForegroundColor: Colors.grey, + ), + onPressed: generateOfficeChart, + child: const Text('Generate Excel Chart'), + ) + ], + ), + ), + ); + } + + Future generateOfficeChart() async { + //Create a Excel document. + + //Creating a workbook. + final Workbook workbook = Workbook(0); + //Adding a Sheet with name to workbook. + final Worksheet sheet1 = workbook.worksheets.addWithName('Budget'); + sheet1.showGridlines = false; + + sheet1.enableSheetCalculations(); + sheet1.getRangeByIndex(1, 1).columnWidth = 19.86; + sheet1.getRangeByIndex(1, 2).columnWidth = 14.38; + sheet1.getRangeByIndex(1, 3).columnWidth = 12.98; + sheet1.getRangeByIndex(1, 4).columnWidth = 12.08; + sheet1.getRangeByIndex(1, 5).columnWidth = 8.82; + sheet1.getRangeByName('A1:A18').rowHeight = 20.2; + + //Adding cell style. + final Style style1 = workbook.styles.add('Style1'); + style1.backColor = '#D9E1F2'; + style1.hAlign = HAlignType.left; + style1.vAlign = VAlignType.center; + style1.bold = true; + + final Style style2 = workbook.styles.add('Style2'); + style2.backColor = '#8EA9DB'; + style2.vAlign = VAlignType.center; + style2.numberFormat = r'[Red](\$#,###)'; + style2.bold = true; + + sheet1.getRangeByName('A10').cellStyle = style1; + sheet1.getRangeByName('B10:D10').cellStyle.backColor = '#D9E1F2'; + sheet1.getRangeByName('B10:D10').cellStyle.hAlign = HAlignType.right; + sheet1.getRangeByName('B10:D10').cellStyle.vAlign = VAlignType.center; + sheet1.getRangeByName('B10:D10').cellStyle.bold = true; + + sheet1.getRangeByName('A11:A17').cellStyle.vAlign = VAlignType.center; + sheet1.getRangeByName('A11:D17').cellStyle.borders.bottom.lineStyle = + LineStyle.thin; + sheet1.getRangeByName('A11:D17').cellStyle.borders.bottom.color = '#BFBFBF'; + + sheet1.getRangeByName('D18').cellStyle = style2; + sheet1.getRangeByName('D18').cellStyle.vAlign = VAlignType.center; + sheet1.getRangeByName('A18:C18').cellStyle.backColor = '#8EA9DB'; + sheet1.getRangeByName('A18:C18').cellStyle.vAlign = VAlignType.center; + sheet1.getRangeByName('A18:C18').cellStyle.bold = true; + sheet1.getRangeByName('A18:C18').numberFormat = r'\$#,###'; + + sheet1.getRangeByIndex(10, 1).setText('Category'); + sheet1.getRangeByIndex(10, 2).setText('Expected cost'); + sheet1.getRangeByIndex(10, 3).setText('Actual Cost'); + sheet1.getRangeByIndex(10, 4).setText('Difference'); + sheet1.getRangeByIndex(11, 1).setText('Venue'); + sheet1.getRangeByIndex(12, 1).setText('Seating & Decor'); + sheet1.getRangeByIndex(13, 1).setText('Technical team'); + sheet1.getRangeByIndex(14, 1).setText('Performers'); + sheet1.getRangeByIndex(15, 1).setText("Performer's transport"); + sheet1.getRangeByIndex(16, 1).setText("Performer's stay"); + sheet1.getRangeByIndex(17, 1).setText('Marketing'); + sheet1.getRangeByIndex(18, 1).setText('Total'); + + sheet1.getRangeByName('B11:D17').numberFormat = r'\$#,###'; + sheet1.getRangeByName('D11').numberFormat = r'[Red](\$#,###)'; + sheet1.getRangeByName('D12').numberFormat = r'[Red](\$#,###)'; + sheet1.getRangeByName('D14').numberFormat = r'[Red](\$#,###)'; + + sheet1.getRangeByName('B11').setNumber(16250); + sheet1.getRangeByName('B12').setNumber(1600); + sheet1.getRangeByName('B13').setNumber(1000); + sheet1.getRangeByName('B14').setNumber(12400); + sheet1.getRangeByName('B15').setNumber(3000); + sheet1.getRangeByName('B16').setNumber(4500); + sheet1.getRangeByName('B17').setNumber(3000); + sheet1.getRangeByName('B18').setFormula('=SUM(B11:B17)'); + + sheet1.getRangeByName('C11').setNumber(17500); + sheet1.getRangeByName('C12').setNumber(1828); + sheet1.getRangeByName('C13').setNumber(800); + sheet1.getRangeByName('C14').setNumber(14000); + sheet1.getRangeByName('C15').setNumber(2600); + sheet1.getRangeByName('C16').setNumber(4464); + sheet1.getRangeByName('C17').setNumber(2700); + sheet1.getRangeByName('C18').setFormula('=SUM(C11:C17)'); + + sheet1.getRangeByName('D11').setFormula('=IF(C11>B11,C11-B11,B11-C11)'); + sheet1.getRangeByName('D12').setFormula('=IF(C12>B12,C12-B12,B12-C12)'); + sheet1.getRangeByName('D13').setFormula('=IF(C13>B13,C13-B13,B13-C13)'); + sheet1.getRangeByName('D14').setFormula('=IF(C14>B14,C14-B14,B14-C14)'); + sheet1.getRangeByName('D15').setFormula('=IF(C15>B15,C15-B15,B15-C15)'); + sheet1.getRangeByName('D16').setFormula('=IF(C16>B16,C16-B16,B16-C16)'); + sheet1.getRangeByName('D17').setFormula('=IF(C17>B17,C17-B17,B17-C17)'); + sheet1.getRangeByName('D18').setFormula('=IF(C18>B18,C18-B18,B18-C18)'); + + // Create chart collection for worksheet. + final ChartCollection charts = ChartCollection(sheet1); + + // Add a chart to the chart collection. + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.pie; + chart.dataRange = sheet1.getRangeByName('A11:B17'); + chart.isSeriesInRows = false; + chart.chartTitle = 'Event Expenses'; + chart.chartTitleArea.bold = true; + chart.chartTitleArea.size = 16; + chart.topRow = 1; + chart.bottomRow = 10; + chart.leftColumn = 1; + chart.rightColumn = 5; + sheet1.charts = charts; + + //Save and launch Excel. + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + await saveAndLaunchFile(bytes, 'ExpenseReport.xlsx'); + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/officechart.dart b/packages/syncfusion_flutter_officechart/lib/officechart.dart new file mode 100644 index 000000000..4dbe8817b --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/officechart.dart @@ -0,0 +1,28 @@ +library officechart; + +import 'dart:convert'; +import 'dart:core'; + +import 'package:archive/archive.dart'; + +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +import 'package:xml/xml.dart'; + +part 'src/chart/chart_axis.dart'; +part 'src/chart/chart_category_axis.dart'; +part 'src/chart/chart_collection.dart'; +part 'src/chart/chart_datalabel.dart'; +part 'src/chart/chart_enum.dart'; +//Src +part 'src/chart/chart_impl.dart'; +part 'src/chart/chart_legend.dart'; +part 'src/chart/chart_plotarea.dart'; +part 'src/chart/chart_serialization.dart'; +part 'src/chart/chart_format.dart'; +part 'src/chart/chart_fomat_impl.dart'; +part 'src/chart/chart_serie.dart'; +part 'src/chart/chart_serie_dataformat.dart'; +part 'src/chart/chartserie_dataformat_impl.dart'; +part 'src/chart/chart_series_collection.dart'; +part 'src/chart/chart_text_area.dart'; +part 'src/chart/chart_value_axis.dart'; diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_axis.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_axis.dart new file mode 100644 index 000000000..79add9163 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_axis.dart @@ -0,0 +1,80 @@ +part of officechart; + +/// Represents an axis on the chart. +class ChartAxis { + /// Represents the parent chart. + late Chart _parentChart; + + /// Represent the chart text area object. + ChartTextArea? _titleArea; + + /// Represents number format. + late String numberFormat; + + /// Represents minimumvalue of Axis. + double _minimumValue = 0; + + /// Represents maximum value of Axis. + double _maximumValue = 0; + + /// Gets or sets a boolean value indicating if the axis has major grid lines. + bool hasMajorGridLines = false; + + /// Automatic minimum selected. + bool _isAutoMin = true; + + /// Automatic minimum selected. + bool _isAutoMax = true; + + /// Gets chart text area object. + ChartTextArea get titleArea { + _titleArea ??= ChartTextArea(_parentChart); + + return _titleArea!; + } + + /// Sets chart text area object. + set titleArea(ChartTextArea? value) { + _titleArea = value; + } + + /// Gets chart axis title. + String? get title { + if (_titleArea == null) { + return null; + } + return _titleArea!.text; + } + + /// Sets chart axis title. + set title(String? value) { + titleArea.text = value; + } + + /// Gets indicates whether chart axis have title or not. + bool get _hasAxisTitle { + return _titleArea != null; + } + + /// Represents minimumvalue of Axis. + double get minimumValue { + return _minimumValue; + } + + /// Sets minimumvalue of Axis. + set minimumValue(double value) { + _minimumValue = value; + _isAutoMin = false; + } + + /// Represents maximumvalue of Axis. + double get maximumValue { + return _maximumValue; + } + + /// Sets maximumvalue of Axis. + set maximumValue(double value) { + _maximumValue = value; + _isAutoMax = false; + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_category_axis.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_category_axis.dart new file mode 100644 index 000000000..f8f229e1a --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_category_axis.dart @@ -0,0 +1,32 @@ +part of officechart; + +/// Represents an axis on the chart. +class ChartCategoryAxis extends ChartAxis { + /// Create an instances of [ChartCategoryAxis] class. + ChartCategoryAxis(Worksheet worksheet, Chart chart) { + _sheet = worksheet; + _chart = chart; + super._parentChart = _chart; + } + + /// Parent worksheet. + // ignore: unused_field + late Worksheet _sheet; + + /// True to cut unused plot area. otherwise False. Default for area and surface charts. + // ignore: prefer_final_fields + bool _isBetween = false; + + /// Parent chart. + late Chart _chart; + + /// sets the category labels for the chart. + // ignore: avoid_setters_without_getters + set _categoryLabels(Range? value) { + final ChartSeriesCollection coll = _chart.series; + final int iLen = coll.count; + for (int i = 0; i < iLen; i++) { + coll[i]._categoryLabels = value; + } + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_collection.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_collection.dart new file mode 100644 index 000000000..2db25e8ad --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_collection.dart @@ -0,0 +1,108 @@ +part of officechart; + +/// Represents the worksheet rows. +class ChartCollection extends ChartHelper { + /// Create a instance of [ChartCollection] class. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// ChartCollection charts = ChartCollection(sheet); + /// ``` + ChartCollection(Worksheet worksheet) { + _worksheet = worksheet; + _innerList = []; + } + + /// Parent worksheet. + late Worksheet _worksheet; + + /// Represents parent worksheet. + Worksheet get worksheet { + return _worksheet; + } + + /// Parent Serializer + ChartSerialization? _chartSerialization; + + /// Inner list. + late List _innerList; + + /// Represents the innerlist + List get innerList { + return _innerList; + } + + /// Returns the count of chart collection. + int get count { + return _innerList.length; + } + + /// Indexer of the class + Chart operator [](dynamic index) => innerList[index]; + + /// Add chart to the chart collection. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// ChartCollection chart = ChartCollection(sheet); + /// chart.add(); + /// sheet.charts = chart; + /// List bytes = workbook.saveAsStream(); + /// File('EmptyChart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + Chart add() { + final Chart chart = Chart(_worksheet); + chart._series = ChartSeriesCollection(_worksheet, chart); + chart._primaryCategoryAxis = ChartCategoryAxis(_worksheet, chart); + chart._primaryValueAxis = ChartValueAxis(_worksheet, chart); + chart._primaryValueAxis.hasMajorGridLines = true; + chart._plotArea = ChartPlotArea(_worksheet, chart); + innerList.add(chart); + chart.name = 'Chart${innerList.length}'; + chart.index = innerList.length; + chart.topRow = 0; + chart.bottomRow = 19; + chart.leftColumn = 0; + chart.rightColumn = 9; + chart.primaryCategoryAxis.numberFormat = 'General'; + chart.primaryValueAxis.numberFormat = 'General'; + _worksheet.chartCount++; + return chart; + } + + /// Serialize the charts. + @override + void serializeChartsSync(Worksheet sheet) { + _chartSerialization ??= ChartSerialization(sheet.workbook); + + _chartSerialization!._saveCharts(sheet); + } + + /// Serialize the charts. + @override + Future serializeCharts(Worksheet sheet) async { + _chartSerialization ??= ChartSerialization(sheet.workbook); + + _chartSerialization!._saveChartsAsync(sheet); + } + + /// Serialize the chart drawings. + @override + void serializeChartDrawingSync(XmlBuilder builder, Worksheet sheet) { + _chartSerialization ??= ChartSerialization(sheet.workbook); + + _chartSerialization!._serializeChartDrawing(builder, sheet); + } + + /// Serialize the chart drawings. + @override + Future serializeChartDrawing( + XmlBuilder builder, Worksheet sheet) async { + _chartSerialization ??= ChartSerialization(sheet.workbook); + + _chartSerialization!._serializeChartDrawingAsync(builder, sheet); + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_datalabel.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_datalabel.dart new file mode 100644 index 000000000..25494e487 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_datalabel.dart @@ -0,0 +1,41 @@ +part of officechart; + +/// Represents the Chart data labels class. +class ChartDataLabels { + /// Create an instances of [ChartDataLabels] class. + ChartDataLabels(ChartSerie serie) { + _parentSerie = serie; + } + + /// Gets or sets a boolean value indicating whether + /// to display data label values. + bool isValue = false; + + /// Gets or sets a boolean value indicating whether to display + /// category name for data labels. + bool isCategoryName = false; + + /// Gets or sets a boolean value indicating whether to display series name for data labels. + bool isSeriesName = false; + + /// Represent the chart text area object. + ChartTextArea? _textArea; + + /// Represents number format. + String? numberFormat; + + /// Represent parent Serie. + late ChartSerie _parentSerie; + + /// Gets chart text area object. + ChartTextArea get textArea { + _textArea ??= ChartTextArea(_parentSerie); + + return _textArea!; + } + + /// Sets chart text area object. + set textArea(ChartTextArea value) { + _textArea = value; + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_enum.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_enum.dart new file mode 100644 index 000000000..640a4ccef --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_enum.dart @@ -0,0 +1,199 @@ +part of officechart; + +/// Specifies the chart type. +enum ExcelChartType { + /// Represents the clustered column chart. + column, + + /// Represnts the 3D column chart + column3D, + + /// Represents the column stacked chart. + columnStacked, + + /// Represents the 100% stacked column chart. + columnStacked100, + + /// Represents the 3D clustered column chart. + columnClustered3D, + + /// Represents the 3D stacked column chart. + columnStacked3D, + + /// Represents the 3D 100% stacked column chart. + columnStacked1003D, + + /// Represents the clustered bar chart. + bar, + + /// Represents the bar stacked chart. + barStacked, + + /// Represents the 100% stacked bar chart. + barStacked100, + + /// Represents the 3D stacked bar chart. + barStacked3D, + + /// Represents the 3D clustered bar chart. + barClustered3D, + + /// Represents the 3D 100% stacked bar chart. + barStacked1003D, + + /// Represents the line chart. + line, + + /// Represents the line stacked chart. + lineStacked, + + /// Represents the 100% stacked line chart. + lineStacked100, + + /// Represents the line chart with markers. + lineMarkers, + + /// Represents the stacked line chart with markers. + lineMarkersStacked, + + /// Represents the 100% stacked line chart with markers. + lineMarkersStacked100, + + /// Represents the 3D line chart. + line3D, + + /// Represents the Pie chart. + pie, + + /// Represents the Pie chart. + pie3D, + + /// Represents the pie of pie chart. + pieOfPie, + + /// Represents the bar of pie chart. + pieBar, + + /// Represents the area chart. + area, + + /// Represents the stacked area chart. + areaStacked, + + /// Represents the 100% stacked area chart. + areaStacked100, + + /// Represents the stock chart with high, low and close values. + stockHighLowClose, + + /// Represents the stock chart with open, high, low and close values. + stockOpenHighLowClose, + + /// Represents the stock chart with volume, high, low and close values. + stockVolumeHighLowClose, + + /// Represents the stock chart with volume, open, high, low and close values. + stockVolumeOpenHighLowClose, + + /// Represents the doughnut chart. + doughnut, + + /// Represents the doughnut Exploded chart. + doughnutExploded, +} + +/// Specifies the line pattern for the border. +enum ExcelChartLinePattern { + /// Solid line. + solid, + + /// Dashed line. + dash, + + /// Alternating long dashed line. + longDash, + + /// Rounded Dotted line. + roundDot, + + /// Squared Dotted line. + squareDot, + + /// Alternating dashes and dots. + dashDot, + + /// Alternating long dashes and dots. + longDashDot, + + /// Long Dash followed by two dots. + longDashDotDot, + + /// No line. + none, +} + +/// Specifies the position of the legend on a chart. +enum ExcelLegendPosition { + ///Represents the bottom of the chart. + bottom, + + ///Represents the upper right-hand corner of the chart border. + corner, + + ///Represents the top of the chart. + top, + + ///Represents the right of the chart. + right, + + ///Represents the left of the chart. + left, + + ///Represents the NotDocked option. + notDocked, +} + +/// Specifies the axis types for charts in Excel. +enum ExcelAxisType { + /// Axis displays categories. + category, + + /// Axis displays values. + value, + + /// Axis displays data series. + serie, +} + +/// Specifies the marker style for a point or series in a line chart, scatter chart, or radar chart. +enum ExcelChartMarkerType { + /// Represents no markers. + none, + + /// Represents square markers. + square, + + /// Represents circular marker + circle, + + /// Represents diamond shaped markers. + diamond, + + /// Represents triangle markers. + triangle, + + /// Represents square markers with X. + xSquare, + + /// Represents dow jones style custom marker + dowJones, + + /// Represents plus sign marker + plusSign, + + /// Represents square markers with asterisk + starSquare, + + /// Represents standard deviation style custome marker + standardDeviation, +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_fomat_impl.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_fomat_impl.dart new file mode 100644 index 000000000..bae84bcc7 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_fomat_impl.dart @@ -0,0 +1,129 @@ +part of officechart; + +///Represents chart format implement class +class _ChartformatImpl implements ChartFormat { + /// Create an instances of [_ChartformatImpl] class. + _ChartformatImpl(Chart chart) { + _firstSliceAngle = 0; + _doughnutHoleSize = 75; + _gapWidth = 150; + _gapDepth = 150; + _pieSecondSize = 75; + _chart = chart; + } + + /// Represents the angle of the first pie-chart or dough-nut chart slice, in degrees (clockwise from vertical). + /// Can be a value from 0 through 360. + late int _firstSliceAngle; + + /// Represents the hole size is expressed as a percentage of the chart size, between 10 and 90 percent. + late int _doughnutHoleSize; + + /// Represents the space between bar/column clusters, as a percentage of the bar/column width in bar/column charts. + /// Represents the space between the primary and secondary sections, in Pie of Pie and Bar of Pie charts. + late int _gapWidth; + + /// Represents or sets the distance between the data series in a 3-D chart, as a percentage of the marker width.( 0 - 500 ) + late int _gapDepth; + + /// Represents percentage of the size of the secondary pie. ( 5 - 200 ). + late int _pieSecondSize; + + /// Parent chart. + late Chart _chart; + + ///Get first slice angle + @override + int get firstSliceAngle { + return _firstSliceAngle; + } + + ///Set first slice angle + @override + set firstSliceAngle(int value) { + if (value < 0 || value > 360) { + throw Exception('First slice angle'); + } + for (int i = 0; i < _chart.series.count; i++) { + final ChartSerie serie1 = _chart.series[i]; + (serie1.serieFormat.commonSerieOptions as _ChartformatImpl) + ._firstSliceAngle = value; + } + } + + ///Get doughnut chart hole size + @override + int get holeSizePercent { + return _doughnutHoleSize; + } + + ///Set doughnut chart hole size + @override + set holeSizePercent(int value) { + if (value < 0 || value > 90) { + throw Exception('DonutHoleSize'); + } + for (int i = 0; i < _chart.series.count; i++) { + final ChartSerie serie1 = _chart.series[i]; + (serie1.serieFormat.commonSerieOptions as _ChartformatImpl) + ._doughnutHoleSize = value; + } + } + + ///Get gap width for chart series + @override + int get gapWidth { + return _gapWidth; + } + + ///Set gapWidth for chart series + @override + set gapWidth(int value) { + if (value < 0 || value > 500) { + throw Exception('gapWidth'); + } + for (int i = 0; i < _chart.series.count; i++) { + final ChartSerie serie1 = _chart.series[i]; + (serie1.serieFormat.commonSerieOptions as _ChartformatImpl)._gapWidth = + value; + } + } + + ///Get second pie/bar size + @override + int get pieSecondSize { + return _pieSecondSize; + } + + ///Set second pie/bar size + @override + set pieSecondSize(int value) { + if (value < 0 || value > 200) { + throw Exception('gapWidth'); + } + for (int i = 0; i < _chart.series.count; i++) { + final ChartSerie serie1 = _chart.series[i]; + (serie1.serieFormat.commonSerieOptions as _ChartformatImpl) + ._pieSecondSize = value; + } + } + + ///Get gap depth for chart series + @override + int get gapDepth { + return _gapDepth; + } + + ///Set gap depth for chart series + @override + set gapDepth(int value) { + if (value < 0 || value > 500) { + throw Exception('gapDepth'); + } + for (int i = 0; i < _chart.series.count; i++) { + final ChartSerie serie1 = _chart.series[i]; + (serie1.serieFormat.commonSerieOptions as _ChartformatImpl)._gapDepth = + value; + } + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_format.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_format.dart new file mode 100644 index 000000000..ab36f046f --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_format.dart @@ -0,0 +1,21 @@ +part of officechart; + +///Represents chart format +class ChartFormat { + /// Represents the angle of first slice in a doughnut or pie chart., in degrees (clockwise from vertical). + /// Can be a value from 0 through 360. + late int firstSliceAngle; + + /// Represents the hole size percentage of a doughnut chart., between 10 and 90 percent. + late int holeSizePercent; + + /// Represents the space between bar/column clusters, as a percentage of the bar/column width in bar/column charts. + /// Represents the space between the primary and secondary sections, in Pie of Pie and Bar of Pie charts. + late int gapWidth; + + /// Represents the distance between the data series in a 3-D chart, as a percentage of the marker width.( 0 - 500 ); + late int gapDepth; + + /// Represents percentage of the size of the secondary pie. ( 5 - 200 ). + late int pieSecondSize; +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_impl.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_impl.dart new file mode 100644 index 000000000..f6f39a720 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_impl.dart @@ -0,0 +1,1156 @@ +part of officechart; + +/// Represents a chart sheet in the workbook. +class Chart { + /// Create an instances of [Chart] class. + Chart(Worksheet sheet) { + _worksheet = sheet; + _createChartTitle(); + hasLegend = true; + } + + /// Represents chart plot area. + ChartPlotArea? _plotArea; + + /// Parent worksheet. + late Worksheet _worksheet; + + /// DataRange for the chart series. + Range? _dataRange; + + /// DataRange for the chart serieValues used in helper methods. + Range? _serieValue; + + /// True if series are in rows in DataRange; + /// otherwise False. + bool _bSeriesInRows = true; + + /// Collection of all the series of this chart. + late ChartSeriesCollection _series; + + /// Get or Set primaryCategoryAxis + late ChartCategoryAxis _primaryCategoryAxis; + + /// Get or Set primary value axis + late ChartValueAxis _primaryValueAxis; + + /// Represent the chart text area object. + ChartTextArea? _textArea; + + /// Represent the default chart title name. + final String _defaultChartTitle = 'Chart Title'; + + /// Represent the indicates whether show the legend or not. + bool _bHasLegend = false; + + /// Represent the chart legend. + ChartLegend? _legend; + + /// Represent the clustered chart collection. + final List _chartsCluster = [ + ExcelChartType.bar, + ExcelChartType.column, + ExcelChartType.columnClustered3D, + ExcelChartType.barClustered3D + ]; + + /// Represent the stacked chart collection. + final List _stackedCharts = [ + ExcelChartType.barStacked, + ExcelChartType.columnStacked, + ExcelChartType.lineStacked, + ExcelChartType.lineMarkersStacked, + ExcelChartType.areaStacked, + ExcelChartType.columnStacked3D, + ExcelChartType.barStacked3D + ]; + + /// Represent 100% charts.Here each value in a series is shown as a portion of 100%. + final List _charts100 = [ + ExcelChartType.columnStacked100, + ExcelChartType.barStacked100, + ExcelChartType.lineStacked100, + ExcelChartType.areaStacked100, + ExcelChartType.columnStacked1003D, + ExcelChartType.barStacked1003D, + ExcelChartType.areaStacked100, + ExcelChartType.lineMarkersStacked100, + ]; + + /// Chart type. + ExcelChartType _chartType = ExcelChartType.column; + + ///Indicated wheather chart type is 3D + bool _is3DChart = false; + + ///Indicates wheather chart type is bar/column + bool _isColumnOrBar = false; + + /// Indicates whether rotation has default value. + bool _isDefaultRotation = true; + + /// Indicates whether elevation has default value. + bool _isdefaultElevation = true; + + ///Represents rotation of 3D chart + int _rotation = 20; + + ///Represents perspective of 3D chart + int _perspective = 15; + + ///Represents elvation angle of 3D chart + int _elevationAngle = 15; + + /// Depth of points relative to width. + int _depthPercent = 100; + + ///Indicates whether the chart axes are at right angles, independent of chart rotation or elevation. + bool _rightAngleAxes = false; + + /// Represent chart index. + late int index; + + /// Represents the chart top row. + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.topRow = 8; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late int topRow; + + /// Represent the chart left colunm. + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.leftColumn = 4; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late int leftColumn; + + /// Represent the chart bottom row. + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.bottomRow = 10; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late int bottomRow; + + /// Represents the chart right colunm. + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.rightColumn = 8; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + int rightColumn = 0; + + /// Excel chart type + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = new ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.pie; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.isSeriesInRows = false; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('ChartType.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + ExcelChartType get chartType { + return _chartType; + } + + set chartType(ExcelChartType value) { + _chartType = value; + if (!_chartType.toString().contains('area')) { + _primaryCategoryAxis._isBetween = true; + } + if (value.toString().endsWith('3D')) { + _is3DChart = true; + } + if (value.toString().contains('column') || + value.toString().contains('bar')) { + _isColumnOrBar = true; + } + } + + /// Gets the boolean value to display the chart legend, True by default. + bool get hasLegend { + return _bHasLegend; + } + + ///sets the boolean value to display the chart legend, True by default. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.hasLegend = false; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('ChartHasLegend.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + set hasLegend(bool value) { + if (_bHasLegend != value) { + _bHasLegend = value; + _legend = value ? ChartLegend(_worksheet, this) : null; + } + } + + /// Represents ChartArea border line property. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.linePattern = ExcelChartLinePattern.dashDot; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('ChartLinePattern.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + ExcelChartLinePattern linePattern = ExcelChartLinePattern.none; + + /// ChartArea border line color property. + /// ``` dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.linePattern = ExcelChartLinePattern.dashDot; + /// chart.linePatternColor = "#FFFF00"; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('ChartLineColor.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + String? linePatternColor; + + /// Gets the chart legend. + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.legend.position = ExcelLegendPosition.bottom; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('ChartLegendPosition.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + ChartLegend? get legend { + return _legend; + } + + /// Represents charts name. + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.name = "Sales"; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('ChartName.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late String name; + + /// Gets chart text area. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = new ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.chartTitleArea.bold = true; + /// chart.chartTitleArea.italic = true; + /// chart.chartTitleArea.size = 15; + /// chart.chartTitleArea.fontName = 'Arial'; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('ChartTitleArea.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + ChartTextArea get chartTitleArea { + if (_textArea == null) { + _createChartTitle(); + } + return _textArea!; + } + + /// Gets chart title. + String? get chartTitle { + return chartTitleArea.text; + } + + /// sets chart title. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = new ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.chartTitle = 'Sales'; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('ChartTitle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + set chartTitle(String? value) { + chartTitleArea.text = value; + } + + /// Gets indicates whether to display chart title or not. + bool get hasTitle { + bool result = false; + if (_textArea != null) { + if (_textArea!.text != null) { + result = true; + } + } + return result; + } + + /// Sets indicates whether to display chart title or not. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = new ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.hasTitle = true; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('ChartHasTitle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + set hasTitle(bool value) { + if (_textArea != null) { + if (value && _textArea!.text == null) { + _textArea!.text = _defaultChartTitle; + } else if (!value) { + _textArea!.text = null; + } + } + } + + /// Gets a boolean value indicating whether the chart has plot area. + bool get _hasPlotArea { + return _plotArea != null; + } + + /// True if chart has a category axis. False otherwise. Read-only. + bool get _isCategoryAxisAvail { + return true; + } + + /// True if chart has a value axis. False otherwise. Read-only. + bool get _isValueAxisAvail { + return true; + } + + /// True if chart has a series in row. False otherwise. Read-only. + bool get isSeriesInRows { + return _bSeriesInRows; + } + + /// Sets chart has a series in row. + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.isSeriesInRows = false; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + set isSeriesInRows(bool value) { + final int iCount = _series.count; + + if (dataRange == null && iCount != 0) { + final Error error = ArgumentError( + 'This property supported only in chart where can detect data range.'); + throw error; + } + + if (_bSeriesInRows != value) { + _bSeriesInRows = value; + + if (iCount != 0) { + _onDataRangeChanged(chartType); + } + } + } + + /// True if chart is a bar chart. False otherwise. Read-only. + bool get isChartBar { + return chartType == ExcelChartType.pie; + } + + /// Get a plotArea + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.isSeriesInRows = false; + /// chart.plotArea.linePattern = ExcelChartLinePattern.dashDot; + /// chart.plotArea.linePatternColor = '#0000FF'; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + ChartPlotArea get plotArea { + return _plotArea!; + } + + /// Get the primaryCategoryAxis + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.isSeriesInRows = false; + /// chart.primaryCategoryAxis.title = 'X axis'; + /// chart.primaryCategoryAxis.titleArea.bold = true; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + ChartCategoryAxis get primaryCategoryAxis { + return _primaryCategoryAxis; + } + + /// Get the primary value axis. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// chart.isSeriesInRows = false; + /// chart.primaryValueAxis.title = 'Y axis'; + /// chart.primaryValueAxis.titleArea.bold = true; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + ChartValueAxis get primaryValueAxis { + return _primaryValueAxis; + } + + /// Gets the collection of series of the chart. Read-only. + ChartSeriesCollection get series { + return _series; + } + + /// Gets the data range for the chart series. + Range? get dataRange { + return _dataRange; + } + + /// sets the data range for the chart series. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.bar; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + set dataRange(Range? value) { + if (_dataRange != value) { + _dataRange = value; + if (value == null) { + return; + } + final ExcelChartType type = chartType; + _onDataRangeChanged(type); + + //ChartSerie serie = m_series[0]; + } + } + + /// sets the rotation of the 3D chart view + /// (the rotation of the plot area around the z-axis, in degrees)-(0 to 360 degrees). + /// The value of this property must be from 0 to 360, except for 3-D bar charts, + /// where the value must be from 0 to 44. The default value is 20. Applies only to 3-D charts. + /// The following code illustrates how to set for 3-D charts. + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A11').text = 'Venue'; + /// sheet.getRangeByName('A12').text = 'Seating & Decor'; + /// sheet.getRangeByName('A13').text = 'Technical Team'; + /// sheet.getRangeByName('A14').text = 'performers'; + /// sheet.getRangeByName('A15').text = "performer's Transport"; + /// sheet.getRangeByName('A16').text = "performer's stay"; + /// sheet.getRangeByName('A17').text = 'Marketing'; + /// sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + /// sheet.getRangeByName('B11').number = 17500; + /// sheet.getRangeByName('B12').number = 1828; + /// sheet.getRangeByName('B13').number = 800; + /// sheet.getRangeByName('B14').number = 14000; + /// sheet.getRangeByName('B15').number = 2600; + /// sheet.getRangeByName('B16').number = 4464; + /// sheet.getRangeByName('B17').number = 2700; + /// final ChartCollection charts = ChartCollection(sheet); + /// final Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.line3D; + /// chart.dataRange = sheet.getRangeByName('A11:B17'); + /// chart.isSeriesInRows = false; + /// chart.chartTitle = 'Line Chart 3D'; + /// chart.rotation = 20; + /// chart.topRow = 8; + /// chart.leftColumn = 1; + /// chart.bottomRow = 23; + /// chart.rightColumn = 8; + /// sheet.charts = charts; + /// final List bytes = workbook.saveAsStream(); + /// saveAsExcel(bytes, 'FLUT_6975_3D_LineChart.xlsx'); + /// ``` + int get rotation { + return _rotation; + } + + ///Set the rotation of 3D view of chart. + set rotation(int value) { + if (value < 0 || value > 360) { + throw Exception('rotation'); + } + _rotation = value; + _isDefaultRotation = false; + } + + /// Get the perspective for the 3D chart view (0 to 100). + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A11').text = 'Venue'; + /// sheet.getRangeByName('A12').text = 'Seating & Decor'; + /// sheet.getRangeByName('A13').text = 'Technical Team'; + /// sheet.getRangeByName('A14').text = 'performers'; + /// sheet.getRangeByName('A15').text = "performer's Transport"; + /// sheet.getRangeByName('A16').text = "performer's stay"; + /// sheet.getRangeByName('A17').text = 'Marketing'; + /// sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + /// sheet.getRangeByName('B11').number = 17500; + /// sheet.getRangeByName('B12').number = 1828; + /// sheet.getRangeByName('B13').number = 800; + /// sheet.getRangeByName('B14').number = 14000; + /// sheet.getRangeByName('B15').number = 2600; + /// sheet.getRangeByName('B16').number = 4464; + /// sheet.getRangeByName('B17').number = 2700; + /// final ChartCollection charts = ChartCollection(sheet); + /// final Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.line3D; + /// chart.dataRange = sheet.getRangeByName('A11:B17'); + /// chart.isSeriesInRows = false; + /// chart.chartTitle = 'Line Chart 3D'; + /// chart.perspective = 45; + /// chart.topRow = 8; + /// chart.leftColumn = 1; + /// chart.bottomRow = 23; + /// chart.rightColumn = 8; + /// sheet.charts = charts; + /// final List bytes = workbook.saveAsStream(); + /// saveAsExcel(bytes, 'FLUT_6975_3D_LineChart.xlsx'); + /// ``` + int get perspective { + return _perspective; + } + + ///set the persepective of 3D chart + set perspective(int value) { + if (value < 0 || value > 100) { + throw Exception('elevation'); + } + + _perspective = value; + } + + /// Get the elevation of the 3-D chart view, in degrees (-90 to +90 degrees). + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A11').text = 'Venue'; + /// sheet.getRangeByName('A12').text = 'Seating & Decor'; + /// sheet.getRangeByName('A13').text = 'Technical Team'; + /// sheet.getRangeByName('A14').text = 'performers'; + /// sheet.getRangeByName('A15').text = "performer's Transport"; + /// sheet.getRangeByName('A16').text = "performer's stay"; + /// sheet.getRangeByName('A17').text = 'Marketing'; + /// sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + /// sheet.getRangeByName('B11').number = 17500; + /// sheet.getRangeByName('B12').number = 1828; + /// sheet.getRangeByName('B13').number = 800; + /// sheet.getRangeByName('B14').number = 14000; + /// sheet.getRangeByName('B15').number = 2600; + /// sheet.getRangeByName('B16').number = 4464; + /// sheet.getRangeByName('B17').number = 2700; + /// final ChartCollection charts = ChartCollection(sheet); + /// final Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.line3D; + /// chart.dataRange = sheet.getRangeByName('A11:B17'); + /// chart.isSeriesInRows = false; + /// chart.chartTitle = 'Line Chart 3D'; + /// chart.elevation = 15; + /// chart.topRow = 8; + /// chart.leftColumn = 1; + /// chart.bottomRow = 23; + /// chart.rightColumn = 8; + /// sheet.charts = charts; + /// final List bytes = workbook.saveAsStream(); + /// saveAsExcel(bytes, 'FLUT_6975_3D_LineChart.xlsx'); + /// ``` + int get elevation { + return _elevationAngle; + } + + ///set the elevation of 3D chart + set elevation(int value) { + if (value < -90 || value > 90) { + throw Exception('elevation'); + } + _elevationAngle = value; + _isdefaultElevation = false; + } + + /// Returns the depth of a 3-D chart as a percentage of the chart width + /// (between 20 and 2000 percent). + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A11').text = 'Venue'; + /// sheet.getRangeByName('A12').text = 'Seating & Decor'; + /// sheet.getRangeByName('A13').text = 'Technical Team'; + /// sheet.getRangeByName('A14').text = 'performers'; + /// sheet.getRangeByName('A15').text = "performer's Transport"; + /// sheet.getRangeByName('A16').text = "performer's stay"; + /// sheet.getRangeByName('A17').text = 'Marketing'; + /// sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + /// sheet.getRangeByName('B11').number = 17500; + /// sheet.getRangeByName('B12').number = 1828; + /// sheet.getRangeByName('B13').number = 800; + /// sheet.getRangeByName('B14').number = 14000; + /// sheet.getRangeByName('B15').number = 2600; + /// sheet.getRangeByName('B16').number = 4464; + /// sheet.getRangeByName('B17').number = 2700; + /// final ChartCollection charts = ChartCollection(sheet); + /// final Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.line3D; + /// chart.dataRange = sheet.getRangeByName('A11:B17'); + /// chart.isSeriesInRows = false; + /// chart.chartTitle = 'Line Chart 3D'; + /// chart.depthPercent = 45; + /// chart.topRow = 8; + /// chart.leftColumn = 1; + /// chart.bottomRow = 23; + /// chart.rightColumn = 8; + /// sheet.charts = charts; + /// final List bytes = workbook.saveAsStream(); + /// saveAsExcel(bytes, 'FLUT_6975_3D_LineChart.xlsx'); + /// ``` + int get depthPercent { + return _depthPercent; + } + + ///set the elevation of 3D chart + set depthPercent(int value) { + if (value < -90 || value > 90) { + throw Exception('elevation'); + } + _depthPercent = value; + } + + /// True if the chart axes are at right angles, independent of chart rotation or elevation. otherwise False. + /// Returns the RightAngleAxes of a 3-D chart as a percentage of the chart width + /// (between 20 and 2000 percent). + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A11').text = 'Venue'; + /// sheet.getRangeByName('A12').text = 'Seating & Decor'; + /// sheet.getRangeByName('A13').text = 'Technical Team'; + /// sheet.getRangeByName('A14').text = 'performers'; + /// sheet.getRangeByName('A15').text = "performer's Transport"; + /// sheet.getRangeByName('A16').text = "performer's stay"; + /// sheet.getRangeByName('A17').text = 'Marketing'; + /// sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + /// sheet.getRangeByName('B11').number = 17500; + /// sheet.getRangeByName('B12').number = 1828; + /// sheet.getRangeByName('B13').number = 800; + /// sheet.getRangeByName('B14').number = 14000; + /// sheet.getRangeByName('B15').number = 2600; + /// sheet.getRangeByName('B16').number = 4464; + /// sheet.getRangeByName('B17').number = 2700; + /// final ChartCollection charts = ChartCollection(sheet); + /// final Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.line3D; + /// chart.dataRange = sheet.getRangeByName('A11:B17'); + /// chart.isSeriesInRows = false; + /// chart.chartTitle = 'Line Chart 3D'; + /// chart.depthPercent = 45; + /// chart.topRow = 8; + /// chart.leftColumn = 1; + /// chart.bottomRow = 23; + /// chart.rightColumn = 8; + ///chart.rightAngleAxes=false; + /// sheet.charts = charts; + /// final List bytes = workbook.saveAsStream(); + /// saveAsExcel(bytes, 'FLUT_6975_3D_LineChart.xlsx'); + /// ``` + bool get rightAngleAxes { + return _rightAngleAxes; + } + + ///set the right angle axes of 3D chart + set rightAngleAxes(bool value) { + _rightAngleAxes = value; + if (!value) { + _isColumnOrBar = false; + } + } + + /// Finds the category range in the specified chart range. + // ignore: unused_element + Range _getCategoryRange( + Range chartValues, Range values, double count, bool bIsInRow) { + final int firstRow = chartValues.row; + final int lastRow = chartValues.lastRow; + final int firstColumn = chartValues.column; + final int lastColumn = chartValues.lastColumn; + Range result; + if (chartValues.count == count) { + values = chartValues; + return chartValues; + } + result = bIsInRow + ? chartValues.worksheet + .getRangeByIndex(firstRow, firstColumn, lastRow, firstColumn) + : chartValues.worksheet + .getRangeByIndex(firstRow, firstColumn, firstRow, lastColumn); + if (firstRow == lastRow && firstColumn == lastColumn) { + values = result; + } else if (firstRow == lastRow) { + values = bIsInRow + ? chartValues.worksheet + .getRangeByIndex(firstRow, firstColumn, lastRow, lastColumn) + : chartValues + ..worksheet.getRangeByIndex(firstRow, firstColumn, lastRow, lastColumn); + } else { + final int add = bIsInRow + ? (firstColumn == lastColumn ? 0 : 1) + : (firstRow == lastRow ? 0 : 1); + values = bIsInRow + ? chartValues.worksheet + .getRangeByIndex(firstRow, firstColumn + add, lastRow, lastColumn) + : chartValues.worksheet.getRangeByIndex( + firstRow + add, firstColumn, lastRow, lastColumn); + } + return result; + } + + /// This method is called if DataRange was changed. + void _onDataRangeChanged(ExcelChartType type) { + if (_dataRange == null) { + _series._clear(); + return; + } + + Range? serieValue, serieNameRange, axisRange; + + serieNameRange = + _getSerieOrAxisRange(_dataRange, _bSeriesInRows, serieValue); + axisRange = _getSerieOrAxisRange(_serieValue, !_bSeriesInRows, _serieValue); + // } + if (!_validateSerieRangeForChartType(_serieValue, type, _bSeriesInRows)) { + final Error error = ArgumentError("Can't set data range."); + throw error; + } + + primaryCategoryAxis._categoryLabels = axisRange; + int iIndex = 0; + + if (serieNameRange != null && axisRange != null) { + iIndex = _bSeriesInRows + ? axisRange.lastRow - axisRange.row + 1 + : axisRange.lastColumn - axisRange.column + 1; + } + + _updateSeriesByDataRange( + _serieValue, serieNameRange, axisRange, iIndex, _bSeriesInRows); + } + + /// Gets data range that represents series name or category axis. + Range? _getSerieOrAxisRange(Range? range, bool bIsInRow, Range? serieRange) { + if (range == null) { + final Error error = ArgumentError('range-Value should not be null'); + throw error; + } + + final int iFirstLen = bIsInRow ? range.row : range.column; + final int iRowColumn = bIsInRow ? range.lastRow : range.lastColumn; + + final int iFirsCount = bIsInRow ? range.column : range.row; + final int iLastCount = bIsInRow ? range.lastColumn : range.lastRow; + + int iIndex = -1; + + bool bIsName = false; + + for (int i = iFirsCount; i < iLastCount && !bIsName; i++) { + final Range curRange = bIsInRow + ? range.worksheet.getRangeByIndex(iRowColumn, i) + : range.worksheet.getRangeByIndex(i, iRowColumn); + + bIsName = (curRange.number != null && + curRange.dateTime == null && + curRange.text == null) || + (curRange.dateTime == null && + curRange.text == null && + curRange.number == null) || + curRange.formula != null; + + if (!bIsName) { + iIndex = i; + } + } + + if (iIndex == -1) { + serieRange = range; + _serieValue = serieRange; + return null; + } + + final Range result = bIsInRow + ? range.worksheet + .getRangeByIndex(iFirstLen, iFirsCount, iRowColumn, iIndex) + : range.worksheet + .getRangeByIndex(iFirsCount, iFirstLen, iIndex, iRowColumn); + + serieRange = bIsInRow + ? range.worksheet.getRangeByIndex( + range.row, result.lastColumn + 1, range.lastRow, range.lastColumn) + : range.worksheet.getRangeByIndex( + result.lastRow + 1, range.column, range.lastRow, range.lastColumn); + _serieValue = serieRange; + return result; + } + + /// Updates series value by data range. + void _updateSeriesByDataRange(Range? serieValue, Range? serieNameRange, + Range? axisRange, int iIndex, bool isSeriesInRows) { + Worksheet? sheet; + if (serieValue != null) { + sheet = serieValue.worksheet; + } + if (sheet == null && serieNameRange != null) { + sheet = serieNameRange.worksheet; + } + sheet ??= _worksheet; + final int iLen = _series.count; + for (int i = 0; i < iLen; i++) { + final Range value = isSeriesInRows + ? sheet.getRangeByIndex(serieValue!.row + i, serieValue.column, + serieValue.row + i, serieValue.lastColumn) + : sheet.getRangeByIndex(serieValue!.row, serieValue.column + i, + serieValue.lastRow, serieValue.column + i); + + final ChartSerie serie = series[i]; + serie.name = 'Serie${i + 1}'; + serie._index = i; + int iAddIndex = iIndex; + serie._values = value; + serie._isDefaultName = true; + if (serieNameRange != null) { + iAddIndex += + isSeriesInRows ? serieNameRange.row : serieNameRange.column; + + String? formula = isSeriesInRows + ? sheet + .getRangeByIndex(iAddIndex + i, serieNameRange.column, + iAddIndex + i, serieNameRange.lastColumn) + .addressGlobal + : sheet + .getRangeByIndex(serieNameRange.row, iAddIndex + i, + serieNameRange.lastRow, iAddIndex + i) + .addressGlobal; + serie._nameOrFormula = formula; + formula = isSeriesInRows + ? sheet + .getRangeByIndex(iAddIndex + i, serieNameRange.column, + iAddIndex + i, serieNameRange.lastColumn) + .text + : sheet + .getRangeByIndex(serieNameRange.row, iAddIndex + i, + serieNameRange.lastRow, iAddIndex + i) + .text; + serie.name = formula; + } + } + } + + /// Validates Series range for min Series count of custom chart type. + bool _validateSerieRangeForChartType( + Range? serieValue, ExcelChartType type, bool isSeriesInRows) { + if (serieValue == null) { + final Error error = ArgumentError('serieValue - Value cannot be null'); + throw error; + } + + final int iSeriesInRangeCount = isSeriesInRows + ? serieValue.lastRow - serieValue.row + 1 + : serieValue.lastColumn - serieValue.column + 1; + final int iSeriesCount = _series.count; + final bool bRemove = iSeriesCount > iSeriesInRangeCount; + final int iStart = bRemove ? iSeriesInRangeCount : iSeriesCount; + final int iLen = bRemove ? iSeriesCount : iSeriesInRangeCount; + + for (int i = iStart; i < iLen; i++) { + if (bRemove) { + _series.innerList.removeAt(iLen - i + iStart - 1); + } else { + _series._add(); + } + } + return true; + } + + /// Create chart text area impl. + void _createChartTitle() { + _textArea = ChartTextArea(this); + } + + /// Indicates whether if given chart type is stacked chart or not. + bool _getIsStacked(ExcelChartType chartType) { + return _stackedCharts.contains(chartType); + } + + /// Indicates whether if given chart type is clustered chart or not. + bool _getIsClustered(ExcelChartType chartType) { + return _chartsCluster.contains(chartType); + } + + /// Indicates whether if given chart type is clustered chart or not. + bool _getIs100(ExcelChartType chartType) { + return _charts100.contains(chartType); + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_legend.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_legend.dart new file mode 100644 index 000000000..a04b3b958 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_legend.dart @@ -0,0 +1,34 @@ +part of officechart; + +/// Represents an legend on the chart. +class ChartLegend { + /// Create an instances of [ChartLegend] class. + ChartLegend(Worksheet worksheet, Chart chart) { + _worksheet = worksheet; + _chart = chart; + } + + /// Parent worksheet. + // ignore: unused_field + late Worksheet _worksheet; + + /// Parent chart. + late Chart _chart; + + /// Gets and sets the chart legend position. + ExcelLegendPosition position = ExcelLegendPosition.right; + + /// Represent the chart text area. + ChartTextArea? _textArea; + + /// Gets the chart legend have text area or not. + bool get _hasTextArea { + return _textArea != null; + } + + /// Gets the chart legend text area. + ChartTextArea get textArea { + _textArea ??= ChartTextArea(_chart); + return _textArea!; + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_plotarea.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_plotarea.dart new file mode 100644 index 000000000..6de5c6e35 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_plotarea.dart @@ -0,0 +1,24 @@ +part of officechart; + +/// Represents chart plot area object. +class ChartPlotArea { + /// Create an instances of [ChartPlotArea] class. + ChartPlotArea(Worksheet worksheet, Chart chart) { + _worksheet = worksheet; + _chart = chart; + } + + /// Parent worksheet. + // ignore: unused_field + late Worksheet _worksheet; + + /// Parent chart. + // ignore: unused_field + late Chart _chart; + + /// Represent PlotArea border line property + ExcelChartLinePattern linePattern = ExcelChartLinePattern.none; + + /// ChartArea border line color property + String? linePatternColor; +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serialization.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serialization.dart new file mode 100644 index 000000000..e9d6455b9 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serialization.dart @@ -0,0 +1,3162 @@ +part of officechart; + +/// Represent the chart serialize class +class ChartSerialization { + /// Create an instances of [ChartSerialization] class. + ChartSerialization(Workbook workbook) { + _workbook = workbook; + } + + /// Workbook to serialize. + late Workbook _workbook; + + final Map _linePattern = { + ExcelChartLinePattern.roundDot: 'sysDot', + ExcelChartLinePattern.squareDot: 'sysDash', + ExcelChartLinePattern.dash: 'dash', + ExcelChartLinePattern.longDash: 'lgDash', + ExcelChartLinePattern.dashDot: 'dashDot', + ExcelChartLinePattern.longDashDot: 'lgDashDot', + ExcelChartLinePattern.longDashDotDot: 'lgDashDotDot' + }; + + /// serializes charts from the worksheet. + void _saveCharts(Worksheet sheet) { + for (final Chart chart in (sheet.charts! as ChartCollection).innerList) { + sheet.workbook.chartCount++; + final XmlBuilder builder = XmlBuilder(); + builder.processing('xml', 'version="1.0"'); + builder.element('c:chartSpace', nest: () { + builder.attribute( + 'xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + builder.attribute('xmlns:r', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + builder.attribute('xmlns:c', + 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + + if (chart._hasPlotArea) { + builder.element('c:roundedCorners', nest: () { + builder.attribute('val', 0); + }); + } + builder.element('c:chart', nest: () { + if (chart.hasTitle) { + _serializeTitle(builder, chart.chartTitleArea); + } + if (chart._is3DChart) { + _serializeView3D(builder, chart); + } + _serializePlotArea(builder, chart); + if (chart.series.count > 0 && chart.hasLegend) { + _serializeLegend(builder, chart.legend!); + } + builder.element('c:plotVisOnly', nest: () { + builder.attribute('val', '1'); + }); + builder.element('c:dispBlanksAs', nest: () { + builder.attribute('val', 'gap'); + }); + }); + _serializeFill( + builder, chart.linePattern, chart.linePatternColor, false); + _serializePrinterSettings(builder, chart); + }); + final String stringXml = builder.buildDocument().copy().toString(); + final List bytes = utf8.encode(stringXml); + _addToArchive(bytes, 'xl/charts/chart${sheet.workbook.chartCount}.xml'); + } + } + + /// serializes chart's legend. + void _serializeLegend(XmlBuilder builder, ChartLegend chartLegend) { + builder.element('c:legend', nest: () { + builder.element('c:legendPos', nest: () { + final ExcelLegendPosition legendPostion = chartLegend.position; + final String positionStr = legendPostion == ExcelLegendPosition.bottom + ? 'b' + : legendPostion == ExcelLegendPosition.left + ? 'l' + : legendPostion == ExcelLegendPosition.right + ? 'r' + : legendPostion == ExcelLegendPosition.top + ? 't' + : 'r'; + builder.attribute('val', positionStr); + }); + if (chartLegend._hasTextArea) { + _serializeDefaultTextAreaProperties(builder, chartLegend.textArea); + } + builder.element('c:layout', nest: () {}); + builder.element('c:overlay', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:spPr', nest: () {}); + }); + } + + /// serializes chart's drawing. + void _serializeChartDrawing(XmlBuilder builder, Worksheet sheet) { + for (final Chart chart in (sheet.charts! as ChartCollection).innerList) { + builder.element('xdr:twoCellAnchor', nest: () { + builder.attribute('editAs', 'twoCell'); + builder.element('xdr:from', nest: () { + builder.element('xdr:col', + nest: chart.leftColumn > 0 ? chart.leftColumn - 1 : 0); + builder.element('xdr:colOff', nest: 0); + builder.element('xdr:row', + nest: chart.topRow > 0 ? chart.topRow - 1 : 0); + builder.element('xdr:rowOff', nest: 0); + }); + + builder.element('xdr:to', nest: () { + builder.element('xdr:col', + nest: chart.rightColumn > 0 + ? chart.rightColumn - 1 + : chart.leftColumn + 7); + builder.element('xdr:colOff', nest: 0); + builder.element('xdr:row', + nest: chart.bottomRow > 0 + ? chart.bottomRow - 1 + : chart.topRow + 15); + builder.element('xdr:rowOff', nest: 0); + }); + + builder.element('xdr:graphicFrame', nest: () { + builder.attribute('macro', ''); + builder.element('xdr:nvGraphicFramePr', nest: () { + builder.element('xdr:cNvPr', nest: () { + builder.attribute( + 'id', 1024 + sheet.workbook.chartCount + chart.index); + builder.attribute( + 'name', 'Chart ${sheet.workbook.chartCount + chart.index}'); + }); + builder.element('xdr:cNvGraphicFramePr', nest: () {}); + }); + builder.element('xdr:xfrm', nest: () { + builder.element('a:off', nest: () { + builder.attribute('x', '0'); + builder.attribute('y', 0); + }); + builder.element('a:ext', nest: () { + builder.attribute('cx', '0'); + builder.attribute('cy', 0); + }); + }); + builder.element('a:graphic', nest: () { + builder.element('a:graphicData', nest: () { + builder.attribute('uri', + 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + + builder.element('c:chart', nest: () { + builder.attribute('p7:id', 'rId${chart.index}'); + builder.attribute('xmlns:p7', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + builder.attribute('xmlns:c', + 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + }); + }); + }); + }); + builder.element('xdr:clientData', nest: () {}); + }); + } + } + + /// serialize default text area properties. + void _serializeDefaultTextAreaProperties( + XmlBuilder builder, ChartTextArea textArea) { + builder.element('c:txPr', nest: () { + builder.element('a:bodyPr ', nest: () {}); + builder.element('a:lstStyle ', nest: () {}); + builder.element('a:p', nest: () { + builder.element('a:pPr', nest: () { + builder.element('a:defRPr', nest: () { + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element('a:solidFill', nest: () { + builder.element('a:srgbClr', nest: () { + builder.attribute('val', textArea.color.replaceAll('#', '')); + }); + }); + } + builder.element('a:latin', nest: () { + builder.attribute('typeface', textArea.fontName); + }); + builder.element('a:cs', nest: () { + builder.attribute('typeface', textArea.fontName); + }); + }); + }); + builder.element('a:endParaRPr', nest: () { + builder.attribute('lang', 'en-US'); + }); + }); + }); + } + + /// serialize printer settings of charts + void _serializePrinterSettings(XmlBuilder builder, Chart chart) { + builder.element('c:printSettings', nest: () { + builder.element('c:headerFooter', nest: () { + builder.attribute('scaleWithDoc', '1'); + builder.attribute('alignWithMargins', '0'); + builder.attribute('differentFirst', '0'); + builder.attribute('differentOddEven', '0'); + }); + builder.element('c:pageMargins', nest: () { + builder.attribute('l', '0.7'); + builder.attribute('r', '0.7'); + builder.attribute('t', '0.75'); + builder.attribute('b', '0.75'); + builder.attribute('header', '0.3'); + builder.attribute('footer', '0.3'); + }); + }); + } + + /// serialize chart title. + void _serializeTitle(XmlBuilder builder, ChartTextArea chartTextArea) { + builder.element('c:title', nest: () { + if (chartTextArea._hasText) { + builder.element('c:tx', nest: () { + builder.element('c:rich', nest: () { + builder.element('a:bodyPr ', nest: () {}); + builder.element('a:lstStyle', nest: () {}); + builder.element('a:p', nest: () { + builder.element('a:pPr', nest: () { + builder.attribute('algn', 'ctr'); + builder.element('a:defRPr', nest: () {}); + }); + builder.element('a:r', nest: () { + _serializeParagraphRunProperties(builder, chartTextArea); + builder.element('a:t', nest: () { + builder.text(chartTextArea.text!); + }); + }); + }); + }); + }); + } + builder.element('c:layout', nest: () {}); + builder.element('c:overlay', nest: () { + builder.attribute('val', chartTextArea._overlay ? '1' : '0'); + }); + _serializeFill(builder, ExcelChartLinePattern.none, null, false); + }); + } + + /// serialize paragraph run properties. + void _serializeParagraphRunProperties( + XmlBuilder builder, ChartTextArea textArea) { + builder.element('a:rPr', nest: () { + builder.attribute('lang', 'en-US'); + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + builder.attribute('baseline', '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element('a:solidFill', nest: () { + builder.element('a:srgbClr', nest: () { + builder.attribute('val', textArea.color.replaceAll('#', '')); + }); + }); + } + builder.element('a:latin', nest: () { + builder.attribute('typeface', textArea.fontName); + }); + builder.element('a:ea', nest: () { + builder.attribute('typeface', textArea.fontName); + }); + builder.element('a:cs', nest: () { + builder.attribute('typeface', textArea.fontName); + }); + }); + } + + /// serialize plotarea of the chart + void _serializePlotArea(XmlBuilder builder, Chart chart) { + builder.element('c:plotArea', nest: () { + builder.element('c:layout', nest: () {}); + if (chart._series.count > 0) { + _serializeMainChartTypeTag(builder, chart); + } else { + _serializeEmptyChart(builder, chart); + _serializeAxes(builder, chart); + } + _serializeFill(builder, chart.plotArea.linePattern, + chart.plotArea.linePatternColor, false); + }); + } + + /// serializes main chart tag. + void _serializeMainChartTypeTag(XmlBuilder builder, Chart chart) { + switch (chart.chartType) { + case ExcelChartType.column: + case ExcelChartType.columnStacked: + case ExcelChartType.columnStacked100: + case ExcelChartType.bar: + case ExcelChartType.barStacked: + case ExcelChartType.barStacked100: + _serializeBarChart(builder, chart); + break; + + case ExcelChartType.line: + case ExcelChartType.lineStacked: + case ExcelChartType.lineMarkers: + case ExcelChartType.lineStacked100: + case ExcelChartType.lineMarkersStacked: + case ExcelChartType.lineMarkersStacked100: + _serializeLineChart(builder, chart); + break; + + case ExcelChartType.area: + case ExcelChartType.areaStacked: + case ExcelChartType.areaStacked100: + _serializeAreaChart(builder, chart); + break; + + case ExcelChartType.pie: + _serializePieChart(builder, chart); + break; + + case ExcelChartType.pieBar: + case ExcelChartType.pieOfPie: + _serializeOfPieChart(builder, chart); + break; + case ExcelChartType.pie3D: + _serializeOfPie3DChart(builder, chart); + break; + case ExcelChartType.line3D: + _serializeLine3DChart(builder, chart); + break; + case ExcelChartType.barStacked1003D: + case ExcelChartType.barStacked3D: + case ExcelChartType.barClustered3D: + case ExcelChartType.columnClustered3D: + case ExcelChartType.columnStacked3D: + case ExcelChartType.columnStacked1003D: + case ExcelChartType.column3D: + _serializeBar3DChart(builder, chart); + break; + + case ExcelChartType.stockHighLowClose: + case ExcelChartType.stockOpenHighLowClose: + case ExcelChartType.stockVolumeHighLowClose: + case ExcelChartType.stockVolumeOpenHighLowClose: + _serializeStockChart(builder, chart); + break; + case ExcelChartType.doughnut: + case ExcelChartType.doughnutExploded: + _serializedoughnutchart(builder, chart); + break; + } + } + + /// serializes Line chart. + void _serializeLineChart(XmlBuilder builder, Chart chart) { + final ExcelChartType type = chart.series[0]._serieType; + builder.element('c:lineChart', nest: () { + _serializeChartGrouping(builder, chart); + builder.element('c:varyColors', nest: () { + builder.attribute('val', 0); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + if (type == ExcelChartType.lineMarkers || + type == ExcelChartType.lineMarkersStacked || + type == ExcelChartType.lineMarkersStacked100) { + builder.element('c:marker', nest: () { + builder.attribute('val', 1); + }); + } + builder.element('c:axId', nest: () { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 57253888); + }); + }); + _serializeAxes(builder, chart); + } + + /// serializes Bar/ColumnClustered chart. + void _serializeBarChart(XmlBuilder builder, Chart chart) { + late int gapwidth; + builder.element('c:barChart', nest: () { + final String strDirection = + chart.chartType.toString().contains('bar') ? 'bar' : 'col'; + builder.element('c:barDir', nest: () { + builder.attribute('val', strDirection); + }); + _serializeChartGrouping(builder, chart); + builder.element('c:varyColors', nest: () { + builder.attribute('val', 0); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + builder.element('c:gapWidth', nest: () { + builder.attribute('val', gapwidth); + }); + if (chart._getIsStacked(chart.chartType) || + chart._getIs100(chart.chartType)) { + builder.element('c:overlap', nest: () { + builder.attribute('val', '100'); + }); + } + builder.element('c:axId', nest: () { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 57253888); + }); + }); + _serializeAxes(builder, chart); + } + + /// serializes Area chart. + void _serializeAreaChart(XmlBuilder builder, Chart chart) { + builder.element('c:areaChart', nest: () { + _serializeChartGrouping(builder, chart); + builder.element('c:varyColors', nest: () { + builder.attribute('val', 0); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + builder.element('c:axId', nest: () { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 57253888); + }); + }); + _serializeAxes(builder, chart); + } + + /// serialize chart grouping. + void _serializeChartGrouping(XmlBuilder builder, Chart chart) { + String strGrouping; + if (chart._getIsClustered(chart.chartType)) { + strGrouping = 'clustered'; + } else if (chart._getIs100(chart.chartType)) { + strGrouping = 'percentStacked'; + } else if (chart._getIsStacked(chart.chartType)) { + strGrouping = 'stacked'; + } else { + strGrouping = 'standard'; + } + builder.element('c:grouping', nest: () { + builder.attribute('val', strGrouping); + }); + } + + /// serializes pie chart. + void _serializePieChart(XmlBuilder builder, Chart chart) { + builder.element('c:pieChart', nest: () { + builder.element('c:varyColors', nest: () { + builder.attribute('val', 1); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + builder.element('c:firstSliceAng', nest: () { + builder.attribute('val', 0); + }); + }); + } + + /// serializes series of chart. + void _serializeSerie(XmlBuilder builder, ChartSerie firstSerie) { + final ExcelChartType type = firstSerie._serieType; + builder.element('c:ser', nest: () { + builder.element('c:idx', nest: () { + builder.attribute('val', firstSerie._index); + }); + builder.element('c:order', nest: () { + builder.attribute('val', firstSerie._index); + }); + if (firstSerie._isDefaultName) { + builder.element('c:tx', nest: () { + final String strName = firstSerie._nameOrFormula; + if (strName.isNotEmpty) { + _serializeStringReference( + builder, strName, firstSerie, 'text', null); + } + }); + } + + if (firstSerie.serieFormat.pieExplosionPercent != 0 || + (type == ExcelChartType.doughnutExploded && + firstSerie._chart.series.count == 1)) { + builder.element('c:explosion', nest: () { + builder.attribute('val', firstSerie.serieFormat.pieExplosionPercent); + }); + } + if (type == ExcelChartType.stockOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + builder.element('c:spPr', nest: () { + builder.element('a:ln', nest: () { + builder.element('a:noFill', nest: () {}); + }); + }); + } else if (type == ExcelChartType.stockHighLowClose) { + if (firstSerie._index == 2) { + builder.element('c:spPr', nest: () { + builder.element('a:ln', nest: () { + builder.attribute('w', '3175'); + builder.element('a:solidFill', nest: () { + builder.element('a:srgbClr', nest: () { + builder.attribute('val', '000000'); + }); + }); + builder.element('a:prstDash', nest: () { + builder.attribute('val', 'solid'); + }); + }); + }); + } else { + builder.element('c:spPr', nest: () { + builder.element('a:ln', nest: () { + builder.element('a:noFill', nest: () {}); + }); + }); + } + } + if (firstSerie.linePattern != ExcelChartLinePattern.none) { + _serializeFill( + builder, firstSerie.linePattern, firstSerie.linePatternColor, true); + } + + _serializeMarker(builder, firstSerie); + + if (firstSerie.dataLabels.isValue) { + builder.element('c:dLbls', nest: () { + if (firstSerie.dataLabels.numberFormat != null && + firstSerie.dataLabels.numberFormat != '' && + firstSerie.dataLabels.numberFormat != 'General') { + builder.element('c:numFmt', nest: () { + builder.attribute( + 'formatCode', + // ignore: unnecessary_null_checks + firstSerie.dataLabels.numberFormat!); + builder.attribute('sourceLinked', '0'); + }); + } + builder.element('c:spPr', nest: () { + builder.element('a:noFill', nest: () {}); + builder.element('a:ln', nest: () { + builder.element('a:noFill', nest: () {}); + }); + builder.element('a:effectLst', nest: () {}); + }); + final ChartTextArea textArea = firstSerie.dataLabels.textArea; + _serializeChartTextArea(builder, textArea); + builder.element('c:showLegendKey', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:showVal', nest: () { + builder.attribute('val', firstSerie.dataLabels.isValue ? 1 : 0); + }); + builder.element('c:showCatName', nest: () { + builder.attribute( + 'val', firstSerie.dataLabels.isCategoryName ? 1 : 0); + }); + builder.element('c:showSerName', nest: () { + builder.attribute( + 'val', firstSerie.dataLabels.isSeriesName ? 1 : 0); + }); + builder.element('c:showPercent', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:showBubbleSize', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:showLeaderLines', nest: () { + builder.attribute('val', 0); + }); + }); + } + if (firstSerie._categoryLabels != null) { + final Range firstRange = firstSerie._chart._worksheet.getRangeByIndex( + firstSerie._categoryLabels!.row, + firstSerie._categoryLabels!.column); + builder.element('c:cat', nest: () { + Worksheet tempSheet = firstSerie._chart._worksheet; + if (firstSerie._categoryLabels!.addressGlobal != '') { + for (final Worksheet sheet + in firstSerie._chart._worksheet.workbook.worksheets.innerList) { + if (firstSerie._categoryLabels!.addressGlobal + .contains(RegExp('${sheet.name}!')) || + firstSerie._categoryLabels!.addressGlobal + .contains(RegExp("${sheet.name}'!"))) { + tempSheet = sheet; + break; + } + } + } + if (firstRange.text == null && firstRange.number != null) { + builder.element('c:numRef', nest: () { + builder.element('c:f', + nest: firstSerie._categoryLabels!.addressGlobal); + final Range firstRange = firstSerie._chart._worksheet + .getRangeByIndex(firstSerie._categoryLabels!.row, + firstSerie._categoryLabels!.column); + builder.element('c:numCache', nest: () { + if (firstRange.numberFormat != null && + firstRange.numberFormat != 'General') { + builder.element('c:formatCode', + nest: firstRange.numberFormat); + } + _serializeNumCacheValues(builder, firstSerie, tempSheet); + }); + }); + } else { + _serializeStringReference( + builder, + firstSerie._categoryLabels!.addressGlobal, + firstSerie, + 'cat', + tempSheet); + } + }); + } + if (firstSerie._values != null) { + builder.element('c:val', nest: () { + builder.element('c:numRef', nest: () { + builder.element('c:f', nest: firstSerie._values!.addressGlobal); + final Range firstRange = firstSerie._chart._worksheet + .getRangeByIndex( + firstSerie._values!.row, firstSerie._values!.column); + Worksheet tempSheet = firstSerie._chart._worksheet; + if (firstSerie._values!.addressGlobal != '') { + for (final Worksheet sheet in firstSerie + ._chart._worksheet.workbook.worksheets.innerList) { + if (firstSerie._values!.addressGlobal + .contains(RegExp('${sheet.name}!')) || + firstSerie._values!.addressGlobal + .contains(RegExp("${sheet.name}'!"))) { + tempSheet = sheet; + break; + } + } + } + builder.element('c:numCache', nest: () { + if (firstRange.numberFormat != null && + firstRange.numberFormat != 'General') { + builder.element('c:formatCode', nest: firstRange.numberFormat); + } else { + builder.element('c:formatCode', nest: 'General'); + } + _serializeNumCacheValues(builder, firstSerie, tempSheet); + }); + }); + }); + } + if (firstSerie._serieType.toString().contains('line') || + firstSerie._serieType.toString().contains('stock')) { + builder.element('c:smooth', nest: () { + builder.attribute('val', 0); + }); + } + }); + } + + /// serializes chart text area. + void _serializeChartTextArea(XmlBuilder builder, ChartTextArea textArea) { + builder.element('c:txPr', nest: () { + builder.element('a:bodyPr ', nest: () {}); + builder.element('a:lstStyle', nest: () {}); + builder.element('a:p', nest: () { + builder.element('a:pPr', nest: () { + builder.element('a:defRPr', nest: () { + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + builder.attribute('baseline', '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element('a:solidFill', nest: () { + builder.element('a:srgbClr', nest: () { + builder.attribute('val', textArea.color.replaceAll('#', '')); + }); + }); + } + builder.element('a:latin', nest: () { + builder.attribute('typeface', textArea.fontName); + }); + builder.element('a:ea', nest: () { + builder.attribute('typeface', textArea.fontName); + }); + builder.element('a:cs', nest: () { + builder.attribute('typeface', textArea.fontName); + }); + }); + }); + }); + }); + } + + /// serializes number cache values. + void _serializeNumCacheValues( + XmlBuilder builder, ChartSerie firstSerie, Worksheet dataSheet) { + final Range? serieRange = firstSerie._values; + if (serieRange != null) { + final int count = serieRange.count; + final int serieStartRow = serieRange.row; + final int serieStartColumn = serieRange.column; + builder.element('c:ptCount', nest: () { + builder.attribute('val', count); + }); + int index = 0; + while (index < count) { + final double? value = dataSheet + .getRangeByIndex(serieStartRow + index, serieStartColumn) + .number; + if (value != null) { + builder.element('c:pt', nest: () { + builder.attribute('idx', index); + builder.element('c:v', nest: value); + }); + } + index++; + } + } + } + + /// serializes string cache reference. + void _serializeStringReference(XmlBuilder builder, String range, + ChartSerie firstSerie, String tagName, Worksheet? dataSheet) { + builder.element('c:strRef', nest: () { + builder.element('c:f', nest: range); + builder.element('c:strCache', nest: () { + if (tagName == 'cat') { + _serializeCategoryTagCacheValues(builder, firstSerie, dataSheet); + } else { + _serializeTextTagCacheValues(builder, firstSerie); + } + }); + }); + } + + /// serializes catergory cache values. + void _serializeCategoryTagCacheValues( + XmlBuilder builder, ChartSerie firstSerie, Worksheet? dataSheet) { + final Range? serieRange = firstSerie._categoryLabels; + if (serieRange != null) { + final int count = serieRange.count; + final int serieStartRow = serieRange.row; + final int serieStartColumn = serieRange.column; + builder.element('c:ptCount', nest: () { + builder.attribute('val', count); + }); + int index = 0; + while (index < count) { + final String? value = dataSheet != null + ? dataSheet + .getRangeByIndex(serieStartRow + index, serieStartColumn) + .text + : ''; + if (value != null) { + builder.element('c:pt', nest: () { + builder.attribute('idx', index); + builder.element('c:v', nest: value); + }); + } + index++; + } + } + } + + /// serializes text cache values. + void _serializeTextTagCacheValues(XmlBuilder builder, ChartSerie firstSerie) { + builder.element('c:ptCount', nest: () { + builder.attribute('val', 1); + }); + builder.element('c:pt', nest: () { + builder.attribute('idx', 0); + if (firstSerie.name != null) { + builder.element('c:v', nest: firstSerie.name); + } + }); + } + + /// serializes fill for the charts. + void _serializeFill(XmlBuilder builder, ExcelChartLinePattern linePattern, + String? lineColor, bool hasSerie) { + builder.element('c:spPr', nest: () { + if (lineColor == null) { + builder.element('a:solidFill', nest: () { + builder.element('a:srgbClr', nest: () { + builder.attribute('val', 'FFFFFF'); + }); + }); + } + builder.element('a:ln', nest: () { + if (linePattern != ExcelChartLinePattern.none) { + if (!hasSerie || lineColor != null) { + builder.element('a:solidFill', nest: () { + builder.element('a:srgbClr', nest: () { + if (lineColor != null) { + builder.attribute('val', lineColor.replaceAll('#', '')); + } else { + builder.attribute('val', '0070C0'); + } + }); + }); + } + if (linePattern != ExcelChartLinePattern.solid) { + builder.element('a:prstDash', nest: () { + builder.attribute('val', _linePattern[linePattern]); + }); + } + } else { + builder.element('a:noFill', nest: () {}); + } + builder.element('a:round', nest: () {}); + }); + }); + } + + /// serializes axies of the series. + void _serializeAxes(XmlBuilder builder, Chart chart) { + if (chart._isCategoryAxisAvail) { + _serializeCategoryAxis(builder, chart.primaryCategoryAxis); + } + if (chart._isValueAxisAvail) { + _serializeValueAxis(builder, chart.primaryValueAxis); + } + } + + /// serializes empty charts. + void _serializeEmptyChart(XmlBuilder builder, Chart chart) { + builder.element('c:barChart', nest: () { + builder.element('c:barDir', nest: () { + builder.attribute('val', 'col'); + }); + builder.element('c:grouping', nest: () { + builder.attribute('val', 'clustered'); + }); + builder.element('c:varyColors', nest: () { + builder.attribute('val', 0); + }); + _serializeEmptyChartDataLabels(builder); + builder.element('c:gapWidth', nest: () { + builder.attribute('val', 150); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 57253888); + }); + }); + } + + /// serializes category axis of the chart. + void _serializeCategoryAxis(XmlBuilder builder, ChartCategoryAxis axis) { + builder.element('c:catAx', nest: () { + builder.element('c:axId', nest: () { + builder.attribute('val', 59983360); + }); + builder.element('c:scaling', nest: () { + builder.element('c:orientation', nest: () { + builder.attribute('val', 'minMax'); + }); + }); + builder.element('c:delete', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:axPos', nest: () { + builder.attribute('val', 'b'); + }); + if (axis._hasAxisTitle) { + _serializeChartTextArea(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element('c:numFmt', nest: () { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }); + } + builder.element('c:majorTickMark', nest: () { + builder.attribute('val', 'out'); + }); + builder.element('c:minorTickMark', nest: () { + builder.attribute('val', 'none'); + }); + builder.element('c:tickLblPos', nest: () { + builder.attribute('val', 'nextTo'); + }); + builder.element('c:spPr', nest: () { + builder.element('a:ln', nest: () {}); + }); + builder.element('c:crossAx', nest: () { + builder.attribute('val', 57253888); + }); + builder.element('c:crosses', nest: () { + builder.attribute('val', 'autoZero'); + }); + builder.element('c:auto', nest: () { + builder.attribute('val', 1); + }); + builder.element('c:lblAlgn', nest: () { + builder.attribute('val', 'ctr'); + }); + builder.element('c:lblOffset', nest: () { + builder.attribute('val', 100); + }); + builder.element('c:noMultiLvlLbl', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:tickMarkSkip', nest: () { + builder.attribute('val', 1); + }); + }); + } + + /// serializes Value axis of the chart. + void _serializeValueAxis(XmlBuilder builder, ChartValueAxis axis) { + builder.element('c:valAx', nest: () { + builder.element('c:axId', nest: () { + builder.attribute('val', 57253888); + }); + builder.element('c:scaling', nest: () { + builder.element('c:orientation', nest: () { + builder.attribute('val', 'minMax'); + }); + if (!axis._isAutoMax) { + builder.element('c:max', nest: () { + builder.attribute('val', axis.maximumValue); + }); + } + if (axis._isAutoMin) { + builder.element('c:min', nest: () { + builder.attribute('val', axis.minimumValue); + }); + } + }); + builder.element('c:delete', nest: () { + builder.attribute('val', '0'); + }); + builder.element('c:axPos', nest: () { + builder.attribute('val', 'l'); + }); + if (axis._hasAxisTitle) { + _serializeChartTextArea(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element('c:numFmt', nest: () { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }); + } + if (axis.hasMajorGridLines) { + builder.element('c:majorGridlines', nest: () {}); + } + builder.element('c:majorTickMark', nest: () { + builder.attribute('val', 'out'); + }); + builder.element('c:minorTickMark', nest: () { + builder.attribute('val', 'none'); + }); + builder.element('c:tickLblPos', nest: () { + builder.attribute('val', 'nextTo'); + }); + builder.element('c:spPr', nest: () { + builder.element('a:ln', nest: () {}); + }); + builder.element('c:crossAx', nest: () { + builder.attribute('val', 59983360); + }); + builder.element('c:crosses', nest: () { + builder.attribute('val', 'autoZero'); + }); + final Chart chart = axis._parentChart; + final String strCrossBetween = + chart.primaryCategoryAxis._isBetween ? 'between' : 'midCat'; + builder.element('c:crossBetween', nest: () { + builder.attribute('val', strCrossBetween); + }); + }); + } + + /// serializes empty chart's datalabels. + void _serializeEmptyChartDataLabels(XmlBuilder builder) { + builder.element('c:dLbls', nest: () { + builder.element('c:showLegendKey', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:showVal', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:showCatName', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:showSerName', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:showPercent', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:showBubbleSize', nest: () { + builder.attribute('val', 0); + }); + }); + } + + /// Add the workbook data with filename to ZipArchive. + void _addToArchive(List data, String fileName) { + final ArchiveFile item = ArchiveFile(fileName, data.length, data); + _workbook.archive.addFile(item); + } + + /// Serialize line 3D Chart. + void _serializeLine3DChart(XmlBuilder builder, Chart chart) { + late int gapdepth; + builder.element('c:line3DChart', nest: () { + _serializeChartGrouping(builder, chart); + builder.element('c:varyColors', nest: () { + builder.attribute('val', 0); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + gapdepth = firstSerie.serieFormat.commonSerieOptions.gapDepth; + } + builder.element('c:gapDepth', nest: () { + builder.attribute('val', gapdepth); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 57253888); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 63149376); + }); + }); + _serializeAxes(builder, chart); + } + + ///Serialize view 3D + void _serializeView3D(XmlBuilder builder, Chart chart) { + final ChartSeriesCollection firstSerie = chart.series; + + builder.element('c:view3D', nest: () { + if (!chart._isdefaultElevation) { + builder.element('c:rotX', nest: () { + builder.attribute('val', chart.elevation); + }); + } + if (firstSerie._innerList[0]._serieType == ExcelChartType.pie3D) { + for (int i = 0; i < chart.series.count; i++) { + chart.rotation = + chart.series[i].serieFormat.commonSerieOptions.firstSliceAngle; + } + } + if (!chart._isDefaultRotation) { + builder.element('c:rotY', nest: () { + builder.attribute('val', chart.rotation); + }); + } + builder.element('c:depthPercent', nest: () { + builder.attribute('val', chart.depthPercent); + }); + builder.element('c:rAngAx', nest: () { + int defaultValue = 0; + + if (chart.rightAngleAxes || chart._isColumnOrBar) { + defaultValue = 1; + } + + builder.attribute('val', defaultValue); + }); + builder.element('perspective', nest: () { + builder.attribute('val', chart.perspective * 2); + }); + }); + } + + /// Serialize bar3D chart. + void _serializeBar3DChart(XmlBuilder builder, Chart chart) { + late int gapdepth; + late int gapwidth; + builder.element('c:bar3DChart', nest: () { + final String strDirection = + chart.chartType.toString().contains('bar') ? 'bar' : 'col'; + + builder.element('c:barDir', nest: () { + builder.attribute('val', strDirection); + }); + + _serializeChartGrouping(builder, chart); + builder.element('c:varyColors', nest: () { + builder.attribute('val', 0); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + gapdepth = firstSerie.serieFormat.commonSerieOptions.gapDepth; + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + + builder.element('c:gapWidth', nest: () { + builder.attribute('val', gapwidth); + }); + builder.element('c:gapDepth', nest: () { + builder.attribute('val', gapdepth); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 57253888); + }); + }); + _serializeAxes(builder, chart); + } + + // Serializes pie of pie or pie of bar chart. + void _serializeOfPieChart(XmlBuilder builder, Chart chart) { + late int gapwidth; + late int pieSecondSize; + final ExcelChartType type = chart.series[0]._serieType; + late String isPieOrBar; + if (type == ExcelChartType.pieOfPie) { + isPieOrBar = 'pie'; + } else if (type == ExcelChartType.pieBar) { + isPieOrBar = 'bar'; + } + builder.element('c:ofPieChart', nest: () { + builder.element('c:ofPieType', nest: () { + builder.attribute('val', isPieOrBar); + }); + builder.element('c:varyColors', nest: () { + builder.attribute('val', 1); + }); + + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + pieSecondSize = firstSerie.serieFormat.commonSerieOptions.pieSecondSize; + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + + builder.element('c:gapWidth', nest: () { + builder.attribute('val', gapwidth); + }); + builder.element('c:secondPieSize', nest: () { + builder.attribute('val', pieSecondSize); + }); + builder.element('c:serLines', nest: () { + builder.element('c:spPr', nest: () { + builder.element('a:ln', nest: () {}); + }); + }); + }); + } + + ///Serialize pie 3D chart. + void _serializeOfPie3DChart(XmlBuilder builder, Chart chart) { + builder.element('c:pie3DChart', nest: () { + builder.element('c:varyColors', nest: () { + builder.attribute('val', 1); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + }); + } + + /// Serializes stock chart. + void _serializeStockChart(XmlBuilder builder, Chart chart) { + final ExcelChartType type = chart.series.innerList[0]._serieType; + if (type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) { + builder.element('c:barChart', nest: () { + builder.element('c:barDir', nest: () { + builder.attribute('val', 'col'); + }); + builder.element('c:grouping', nest: () { + builder.attribute('val', 'clustered'); + }); + + builder.element('c:varyColors', nest: () { + builder.attribute('val', 0); + }); + + final ChartSerie firstSerie = chart.series[0]; + _serializeSerie(builder, firstSerie); + + builder.element('c:gapWidth', nest: () { + builder.attribute( + 'val', firstSerie.serieFormat.commonSerieOptions.gapWidth); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 57253888); + }); + }); + } + builder.element('c:stockChart', nest: () { + if (type == ExcelChartType.stockHighLowClose || + type == ExcelChartType.stockOpenHighLowClose) { + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + } else if (type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + for (int i = 0; i < chart.series.count; i++) { + if ((type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) && + chart.series.innerList[0] != chart.series.innerList[i]) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + } + } + + builder.element('c:hiLowLines', nest: () { + builder.element('c:spPr', nest: () { + builder.element('a:ln', nest: () { + builder.element('a:solidFill', nest: () { + builder.element('a:srgbClr', nest: () { + builder.attribute('val', '000000'); + }); + }); + builder.element('a:prstDash', nest: () { + builder.attribute('val', 'solid'); + }); + }); + }); + }); + if (type == ExcelChartType.stockOpenHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + builder.element('c:upDownBars', nest: () { + builder.element('c:gapWidth', nest: () { + builder.attribute('val', '100'); + }); + builder.element('c:upBars', nest: () { + builder.element('c:spPr', nest: () { + builder.element('a:solidFill', nest: () { + builder.element('a:srgbClr', nest: () { + builder.attribute('val', 'FFFFFF'); + }); + }); + }); + }); + builder.element('c:downBars', nest: () { + builder.element('c:spPr', nest: () { + builder.element('a:solidFill', nest: () { + builder.element('a:srgbClr', nest: () { + builder.attribute('val', '000000'); + }); + }); + }); + }); + }); + } + if (chart.series[0].serieFormat.markerStyle != + ExcelChartMarkerType.none) { + builder.element('c:marker', nest: () { + builder.attribute('val', 1); + }); + } + + builder.element('c:axId', nest: () { + builder.attribute('val', 62908672); + }); + builder.element('c:axId', nest: () { + builder.attribute('val', 61870848); + }); + }); + if (type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) { + _serializeAxesforStockChart(builder, chart, 59983360, 57253888, true); + _serializeAxesforStockChart(builder, chart, 62908672, 61870848, false); + } else { + _serializeAxesforStockChart(builder, chart, 62908672, 61870848, true); + } + } + + ///serialize stock axes + void _serializeAxesforStockChart( + XmlBuilder builder, Chart chart, int axisId, int crossAx, bool isBar) { + if (chart._isCategoryAxisAvail) { + _serializeCategoryAxisForStock( + builder, chart.primaryCategoryAxis, axisId, crossAx, isBar); + } + if (chart._isValueAxisAvail) { + _serializeValueAxisForStockchart( + builder, chart.primaryCategoryAxis, axisId, crossAx, isBar); + } + } + + ///Serialize catogory axis for stock chart + void _serializeCategoryAxisForStock(XmlBuilder builder, + ChartCategoryAxis axis, int axisId, int crossAx, bool isBar) { + builder.element('c:catAx', nest: () { + builder.element('c:axId', nest: () { + builder.attribute('val', axisId); + }); + builder.element('c:scaling', nest: () { + builder.element('c:orientation', nest: () { + builder.attribute('val', 'minMax'); + }); + }); + int delete = 0; + String axpos = 'b'; + if (!isBar) { + delete = 1; + axpos = 't'; + } + builder.element('c:delete', nest: () { + builder.attribute('val', delete); + }); + builder.element('c:axPos', nest: () { + builder.attribute('val', axpos); + }); + if (axis._hasAxisTitle) { + _serializeChartTextArea(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element('c:numFmt', nest: () { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }); + } + builder.element('c:majorTickMark', nest: () { + builder.attribute('val', 'out'); + }); + builder.element('c:minorTickMark', nest: () { + builder.attribute('val', 'none'); + }); + builder.element('c:tickLblPos', nest: () { + builder.attribute('val', 'nextTo'); + }); + builder.element('c:spPr', nest: () { + builder.element('a:ln', nest: () { + if (!isBar) { + builder.attribute('w', 12700); + } + }); + }); + builder.element('c:crossAx', nest: () { + builder.attribute('val', crossAx); + }); + builder.element('c:crosses', nest: () { + builder.attribute('val', 'autoZero'); + }); + builder.element('c:auto', nest: () { + builder.attribute('val', 1); + }); + builder.element('c:lblAlgn', nest: () { + builder.attribute('val', 'ctr'); + }); + builder.element('c:lblOffset', nest: () { + builder.attribute('val', 100); + }); + builder.element('c:noMultiLvlLbl', nest: () { + builder.attribute('val', 0); + }); + builder.element('c:tickMarkSkip', nest: () { + builder.attribute('val', 1); + }); + }); + } + + ///Serialize value axis for stock chart + void _serializeValueAxisForStockchart(XmlBuilder builder, + ChartCategoryAxis axis, int axisId, int crossAx, bool isBar) { + builder.element('c:valAx', nest: () { + builder.element('c:axId', nest: () { + builder.attribute('val', crossAx); + }); + builder.element('c:scaling', nest: () { + builder.element('c:orientation', nest: () { + builder.attribute('val', 'minMax'); + }); + if (!axis._isAutoMax) { + builder.element('c:max', nest: () { + builder.attribute('val', axis.maximumValue); + }); + } + if (axis._isAutoMin) { + builder.element('c:min', nest: () { + builder.attribute('val', axis.minimumValue); + }); + } + }); + builder.element('c:delete', nest: () { + builder.attribute('val', '0'); + }); + String axpos = 'l'; + + if (!isBar) { + axpos = 'r'; + } + + builder.element('c:axPos', nest: () { + builder.attribute('val', axpos); + }); + + if (axpos == 'l') { + builder.element('c:majorGridlines', nest: () {}); + } + + if (axis._hasAxisTitle) { + _serializeChartTextArea(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element('c:numFmt', nest: () { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }); + } + if (axis.hasMajorGridLines) { + builder.element('c:majorGridlines', nest: () {}); + } + builder.element('c:majorTickMark', nest: () { + builder.attribute('val', 'out'); + }); + builder.element('c:minorTickMark', nest: () { + builder.attribute('val', 'none'); + }); + builder.element('c:tickLblPos', nest: () { + builder.attribute('val', 'nextTo'); + }); + builder.element('c:spPr', nest: () { + builder.element('a:ln', nest: () {}); + }); + builder.element('c:crossAx', nest: () { + builder.attribute('val', axisId); + }); + String crosses = 'autoZero'; + if (!isBar) { + crosses = 'max'; + } + builder.element('c:crosses', nest: () { + builder.attribute('val', crosses); + }); + final Chart chart = axis._parentChart; + final String strCrossBetween = + chart.primaryCategoryAxis._isBetween ? 'between' : 'midCat'; + builder.element('c:crossBetween', nest: () { + builder.attribute('val', strCrossBetween); + }); + }); + } + + ///Serialize doughnut/doughnut_exploded charts + void _serializedoughnutchart(XmlBuilder builder, Chart chart) { + late int doughnutHoleSize; + late int firstSliceAngle; + + builder.element('c:doughnutChart', nest: () { + builder.element('c:varyColors', nest: () { + builder.attribute('val', 1); + }); + + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + firstSliceAngle = + firstSerie.serieFormat.commonSerieOptions.firstSliceAngle; + doughnutHoleSize = + firstSerie.serieFormat.commonSerieOptions.holeSizePercent; + } + builder.element('c:firstSliceAng', nest: () { + builder.attribute('val', firstSliceAngle); + }); + builder.element('c:holeSize', nest: () { + builder.attribute('val', doughnutHoleSize); + }); + }); + } + + ///Serialize marker for stock and line charts + void _serializeMarker(XmlBuilder builder, ChartSerie firstSerie) { + final ExcelChartType type = firstSerie._serieType; + + if ((firstSerie.serieFormat.markerStyle == ExcelChartMarkerType.none) && + (type == ExcelChartType.line || + type == ExcelChartType.lineStacked || + type == ExcelChartType.lineStacked100 || + type == ExcelChartType.stockHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockOpenHighLowClose)) { + builder.element('c:marker', nest: () { + builder.element('c:symbol', nest: () { + builder.attribute('val', 'none'); + }); + }); + } else if ((firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.none) && + (type == ExcelChartType.lineMarkers || + type == ExcelChartType.lineMarkersStacked || + type == ExcelChartType.lineMarkersStacked100)) { + builder.element('c:marker', nest: () { + builder.element('c:symbol', nest: () { + builder.attribute('val', 'circle'); + }); + builder.element('c:size', nest: () { + builder.attribute('val', '5'); + }); + }); + } else if (firstSerie.serieFormat.markerStyle != + ExcelChartMarkerType.none) { + final String markerBackgroundColor = + firstSerie.serieFormat.markerBackgroundColor.replaceAll('#', ''); + final String markerBorderColor = + firstSerie.serieFormat.markerBorderColor.replaceAll('#', ''); + late String exclMarkertype; + if (firstSerie.serieFormat.markerStyle == ExcelChartMarkerType.diamond) { + exclMarkertype = 'diamond'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.circle) { + exclMarkertype = 'circle'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.xSquare) { + exclMarkertype = 'x'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.dowJones) { + exclMarkertype = 'dot'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.square) { + exclMarkertype = 'square'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.plusSign) { + exclMarkertype = 'plus'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.standardDeviation) { + exclMarkertype = 'triangle'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.starSquare) { + exclMarkertype = 'star'; + } + + builder.element('c:marker', nest: () { + builder.element('c:symbol', nest: () { + builder.attribute('val', exclMarkertype); + }); + builder.element('c:size', nest: () { + builder.attribute('val', '5'); + }); + builder.element('c:spPr', nest: () { + builder.element('a:solidFill', nest: () { + builder.element('a:srgbClr', nest: () { + builder.attribute('val', markerBackgroundColor); + }); + }); + builder.element('a:ln', nest: () { + builder.element('a:solidFill', nest: () { + builder.element('a:srgbClr', nest: () { + builder.attribute('val', markerBorderColor); + }); + }); + }); + }); + }); + } + } + + /// serializes charts from the worksheet. + Future _saveChartsAsync(Worksheet sheet) async { + for (final Chart chart in (sheet.charts! as ChartCollection).innerList) { + sheet.workbook.chartCount++; + final XmlBuilder builder = XmlBuilder(); + builder.processing('xml', 'version="1.0"'); + builder.element('c:chartSpace', nest: () async { + builder.attribute( + 'xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + builder.attribute('xmlns:r', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + builder.attribute('xmlns:c', + 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + + if (chart._hasPlotArea) { + builder.element('c:roundedCorners', nest: () async { + builder.attribute('val', 0); + }); + } + builder.element('c:chart', nest: () async { + if (chart.hasTitle) { + _serializeTitleAsync(builder, chart.chartTitleArea); + } + if (chart._is3DChart) { + _serializeView3DAsync(builder, chart); + } + _serializePlotAreaAsync(builder, chart); + if (chart.series.count > 0 && chart.hasLegend) { + _serializeLegendAsync(builder, chart.legend!); + } + builder.element('c:plotVisOnly', nest: () async { + builder.attribute('val', '1'); + }); + builder.element('c:dispBlanksAs', nest: () async { + builder.attribute('val', 'gap'); + }); + }); + _serializeFillAsync( + builder, chart.linePattern, chart.linePatternColor, false); + _serializePrinterSettingsAsync(builder, chart); + }); + final String stringXml = builder.buildDocument().copy().toString(); + final List bytes = utf8.encode(stringXml); + _addToArchiveAsync( + bytes, 'xl/charts/chart${sheet.workbook.chartCount}.xml'); + } + } + + /// serializes chart's legend. + Future _serializeLegendAsync( + XmlBuilder builder, ChartLegend chartLegend) async { + builder.element('c:legend', nest: () async { + builder.element('c:legendPos', nest: () async { + final ExcelLegendPosition legendPostion = chartLegend.position; + final String positionStr = legendPostion == ExcelLegendPosition.bottom + ? 'b' + : legendPostion == ExcelLegendPosition.left + ? 'l' + : legendPostion == ExcelLegendPosition.right + ? 'r' + : legendPostion == ExcelLegendPosition.top + ? 't' + : 'r'; + builder.attribute('val', positionStr); + }); + if (chartLegend._hasTextArea) { + _serializeDefaultTextAreaPropertiesAsync(builder, chartLegend.textArea); + } + builder.element('c:layout', nest: () async {}); + builder.element('c:overlay', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:spPr', nest: () async {}); + }); + } + + /// serializes chart's drawing. + Future _serializeChartDrawingAsync( + XmlBuilder builder, Worksheet sheet) async { + for (final Chart chart in (sheet.charts! as ChartCollection).innerList) { + builder.element('xdr:twoCellAnchor', nest: () async { + builder.attribute('editAs', 'twoCell'); + builder.element('xdr:from', nest: () async { + builder.element('xdr:col', + nest: chart.leftColumn > 0 ? chart.leftColumn - 1 : 0); + builder.element('xdr:colOff', nest: 0); + builder.element('xdr:row', + nest: chart.topRow > 0 ? chart.topRow - 1 : 0); + builder.element('xdr:rowOff', nest: 0); + }); + + builder.element('xdr:to', nest: () async { + builder.element('xdr:col', + nest: chart.rightColumn > 0 + ? chart.rightColumn - 1 + : chart.leftColumn + 7); + builder.element('xdr:colOff', nest: 0); + builder.element('xdr:row', + nest: chart.bottomRow > 0 + ? chart.bottomRow - 1 + : chart.topRow + 15); + builder.element('xdr:rowOff', nest: 0); + }); + + builder.element('xdr:graphicFrame', nest: () async { + builder.attribute('macro', ''); + builder.element('xdr:nvGraphicFramePr', nest: () async { + builder.element('xdr:cNvPr', nest: () async { + builder.attribute( + 'id', 1024 + sheet.workbook.chartCount + chart.index); + builder.attribute( + 'name', 'Chart ${sheet.workbook.chartCount + chart.index}'); + }); + builder.element('xdr:cNvGraphicFramePr', nest: () async {}); + }); + builder.element('xdr:xfrm', nest: () async { + builder.element('a:off', nest: () async { + builder.attribute('x', '0'); + builder.attribute('y', 0); + }); + builder.element('a:ext', nest: () async { + builder.attribute('cx', '0'); + builder.attribute('cy', 0); + }); + }); + builder.element('a:graphic', nest: () async { + builder.element('a:graphicData', nest: () async { + builder.attribute('uri', + 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + + builder.element('c:chart', nest: () async { + builder.attribute('p7:id', 'rId${chart.index}'); + builder.attribute('xmlns:p7', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + builder.attribute('xmlns:c', + 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + }); + }); + }); + }); + builder.element('xdr:clientData', nest: () async {}); + }); + } + } + + /// serialize default text area properties. + Future _serializeDefaultTextAreaPropertiesAsync( + XmlBuilder builder, ChartTextArea textArea) async { + builder.element('c:txPr', nest: () async { + builder.element('a:bodyPr ', nest: () async {}); + builder.element('a:lstStyle ', nest: () async {}); + builder.element('a:p', nest: () async { + builder.element('a:pPr', nest: () async { + builder.element('a:defRPr', nest: () async { + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element('a:solidFill', nest: () async { + builder.element('a:srgbClr', nest: () async { + builder.attribute('val', textArea.color.replaceAll('#', '')); + }); + }); + } + builder.element('a:latin', nest: () async { + builder.attribute('typeface', textArea.fontName); + }); + builder.element('a:cs', nest: () async { + builder.attribute('typeface', textArea.fontName); + }); + }); + }); + builder.element('a:endParaRPr', nest: () async { + builder.attribute('lang', 'en-US'); + }); + }); + }); + } + + /// serialize printer settings of charts + Future _serializePrinterSettingsAsync( + XmlBuilder builder, Chart chart) async { + builder.element('c:printSettings', nest: () async { + builder.element('c:headerFooter', nest: () async { + builder.attribute('scaleWithDoc', '1'); + builder.attribute('alignWithMargins', '0'); + builder.attribute('differentFirst', '0'); + builder.attribute('differentOddEven', '0'); + }); + builder.element('c:pageMargins', nest: () async { + builder.attribute('l', '0.7'); + builder.attribute('r', '0.7'); + builder.attribute('t', '0.75'); + builder.attribute('b', '0.75'); + builder.attribute('header', '0.3'); + builder.attribute('footer', '0.3'); + }); + }); + } + + /// serialize chart title. + Future _serializeTitleAsync( + XmlBuilder builder, ChartTextArea chartTextArea) async { + builder.element('c:title', nest: () async { + if (chartTextArea._hasText) { + builder.element('c:tx', nest: () async { + builder.element('c:rich', nest: () async { + builder.element('a:bodyPr ', nest: () async {}); + builder.element('a:lstStyle', nest: () async {}); + builder.element('a:p', nest: () async { + builder.element('a:pPr', nest: () async { + builder.attribute('algn', 'ctr'); + builder.element('a:defRPr', nest: () async {}); + }); + builder.element('a:r', nest: () async { + _serializeParagraphRunPropertiesAsync(builder, chartTextArea); + builder.element('a:t', nest: () async { + builder.text(chartTextArea.text!); + }); + }); + }); + }); + }); + } + builder.element('c:layout', nest: () async {}); + builder.element('c:overlay', nest: () async { + builder.attribute('val', chartTextArea._overlay ? '1' : '0'); + }); + _serializeFillAsync(builder, ExcelChartLinePattern.none, null, false); + }); + } + + /// serialize paragraph run properties. + Future _serializeParagraphRunPropertiesAsync( + XmlBuilder builder, ChartTextArea textArea) async { + builder.element('a:rPr', nest: () async { + builder.attribute('lang', 'en-US'); + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + builder.attribute('baseline', '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element('a:solidFill', nest: () async { + builder.element('a:srgbClr', nest: () async { + builder.attribute('val', textArea.color.replaceAll('#', '')); + }); + }); + } + builder.element('a:latin', nest: () async { + builder.attribute('typeface', textArea.fontName); + }); + builder.element('a:ea', nest: () async { + builder.attribute('typeface', textArea.fontName); + }); + builder.element('a:cs', nest: () async { + builder.attribute('typeface', textArea.fontName); + }); + }); + } + + /// serialize plotarea of the chart + Future _serializePlotAreaAsync(XmlBuilder builder, Chart chart) async { + builder.element('c:plotArea', nest: () async { + builder.element('c:layout', nest: () async {}); + if (chart._series.count > 0) { + _serializeMainChartTypeTagAsync(builder, chart); + } else { + _serializeEmptyChartAsync(builder, chart); + _serializeAxesAsync(builder, chart); + } + _serializeFillAsync(builder, chart.plotArea.linePattern, + chart.plotArea.linePatternColor, false); + }); + } + + /// serializes main chart tag. + Future _serializeMainChartTypeTagAsync( + XmlBuilder builder, Chart chart) async { + switch (chart.chartType) { + case ExcelChartType.column: + case ExcelChartType.columnStacked: + case ExcelChartType.columnStacked100: + case ExcelChartType.bar: + case ExcelChartType.barStacked: + case ExcelChartType.barStacked100: + _serializeBarChartAsync(builder, chart); + break; + + case ExcelChartType.line: + case ExcelChartType.lineStacked: + case ExcelChartType.lineMarkers: + case ExcelChartType.lineStacked100: + case ExcelChartType.lineMarkersStacked: + case ExcelChartType.lineMarkersStacked100: + _serializeLineChartAsync(builder, chart); + break; + + case ExcelChartType.area: + case ExcelChartType.areaStacked: + case ExcelChartType.areaStacked100: + _serializeAreaChartAsync(builder, chart); + break; + + case ExcelChartType.pie: + _serializePieChartAsync(builder, chart); + break; + + case ExcelChartType.pieBar: + case ExcelChartType.pieOfPie: + _serializeOfPieChartAsync(builder, chart); + break; + case ExcelChartType.pie3D: + _serializeOfPie3DChartAsync(builder, chart); + break; + case ExcelChartType.line3D: + _serializeLine3DChartAsync(builder, chart); + break; + case ExcelChartType.barStacked1003D: + case ExcelChartType.barStacked3D: + case ExcelChartType.barClustered3D: + case ExcelChartType.columnClustered3D: + case ExcelChartType.columnStacked3D: + case ExcelChartType.columnStacked1003D: + case ExcelChartType.column3D: + _serializeBar3DChartAsync(builder, chart); + break; + + case ExcelChartType.stockHighLowClose: + case ExcelChartType.stockOpenHighLowClose: + case ExcelChartType.stockVolumeHighLowClose: + case ExcelChartType.stockVolumeOpenHighLowClose: + _serializeStockChartAsync(builder, chart); + break; + case ExcelChartType.doughnut: + case ExcelChartType.doughnutExploded: + _serializedoughnutchartAsync(builder, chart); + break; + } + } + + /// serializes Line chart. + Future _serializeLineChartAsync(XmlBuilder builder, Chart chart) async { + final ExcelChartType type = chart.series[0]._serieType; + builder.element('c:lineChart', nest: () async { + _serializeChartGroupingAsync(builder, chart); + builder.element('c:varyColors', nest: () async { + builder.attribute('val', 0); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + if (type == ExcelChartType.lineMarkers || + type == ExcelChartType.lineMarkersStacked || + type == ExcelChartType.lineMarkersStacked100) { + builder.element('c:marker', nest: () async { + builder.attribute('val', 1); + }); + } + builder.element('c:axId', nest: () async { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 57253888); + }); + }); + _serializeAxesAsync(builder, chart); + } + + /// serializes Bar/ColumnClustered chart. + Future _serializeBarChartAsync(XmlBuilder builder, Chart chart) async { + late int gapwidth; + builder.element('c:barChart', nest: () async { + final String strDirection = + chart.chartType.toString().contains('bar') ? 'bar' : 'col'; + builder.element('c:barDir', nest: () async { + builder.attribute('val', strDirection); + }); + _serializeChartGroupingAsync(builder, chart); + builder.element('c:varyColors', nest: () async { + builder.attribute('val', 0); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + builder.element('c:gapWidth', nest: () async { + builder.attribute('val', gapwidth); + }); + if (chart._getIsStacked(chart.chartType) || + chart._getIs100(chart.chartType)) { + builder.element('c:overlap', nest: () async { + builder.attribute('val', '100'); + }); + } + builder.element('c:axId', nest: () async { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 57253888); + }); + }); + _serializeAxesAsync(builder, chart); + } + + /// serializes Area chart. + Future _serializeAreaChartAsync(XmlBuilder builder, Chart chart) async { + builder.element('c:areaChart', nest: () async { + _serializeChartGroupingAsync(builder, chart); + builder.element('c:varyColors', nest: () async { + builder.attribute('val', 0); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + builder.element('c:axId', nest: () async { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 57253888); + }); + }); + _serializeAxesAsync(builder, chart); + } + + /// serialize chart grouping. + Future _serializeChartGroupingAsync( + XmlBuilder builder, Chart chart) async { + String strGrouping; + if (chart._getIsClustered(chart.chartType)) { + strGrouping = 'clustered'; + } else if (chart._getIs100(chart.chartType)) { + strGrouping = 'percentStacked'; + } else if (chart._getIsStacked(chart.chartType)) { + strGrouping = 'stacked'; + } else { + strGrouping = 'standard'; + } + builder.element('c:grouping', nest: () async { + builder.attribute('val', strGrouping); + }); + } + + /// serializes pie chart. + Future _serializePieChartAsync(XmlBuilder builder, Chart chart) async { + builder.element('c:pieChart', nest: () async { + builder.element('c:varyColors', nest: () async { + builder.attribute('val', 1); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + builder.element('c:firstSliceAng', nest: () async { + builder.attribute('val', 0); + }); + }); + } + + /// serializes series of chart. + Future _serializeSerieAsync( + XmlBuilder builder, ChartSerie firstSerie) async { + final ExcelChartType type = firstSerie._serieType; + builder.element('c:ser', nest: () async { + builder.element('c:idx', nest: () async { + builder.attribute('val', firstSerie._index); + }); + builder.element('c:order', nest: () async { + builder.attribute('val', firstSerie._index); + }); + if (firstSerie._isDefaultName) { + builder.element('c:tx', nest: () async { + final String strName = firstSerie._nameOrFormula; + if (strName.isNotEmpty) { + _serializeStringReferenceAsync( + builder, strName, firstSerie, 'text', null); + } + }); + } + + if (firstSerie.serieFormat.pieExplosionPercent != 0 || + (type == ExcelChartType.doughnutExploded && + firstSerie._chart.series.count == 1)) { + builder.element('c:explosion', nest: () async { + builder.attribute('val', firstSerie.serieFormat.pieExplosionPercent); + }); + } + if (type == ExcelChartType.stockOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + builder.element('c:spPr', nest: () async { + builder.element('a:ln', nest: () async { + builder.element('a:noFill', nest: () async {}); + }); + }); + } else if (type == ExcelChartType.stockHighLowClose) { + if (firstSerie._index == 2) { + builder.element('c:spPr', nest: () async { + builder.element('a:ln', nest: () async { + builder.attribute('w', '3175'); + builder.element('a:solidFill', nest: () async { + builder.element('a:srgbClr', nest: () async { + builder.attribute('val', '000000'); + }); + }); + builder.element('a:prstDash', nest: () async { + builder.attribute('val', 'solid'); + }); + }); + }); + } else { + builder.element('c:spPr', nest: () async { + builder.element('a:ln', nest: () async { + builder.element('a:noFill', nest: () async {}); + }); + }); + } + } + if (firstSerie.linePattern != ExcelChartLinePattern.none) { + _serializeFillAsync( + builder, firstSerie.linePattern, firstSerie.linePatternColor, true); + } + + _serializeMarkerAsync(builder, firstSerie); + + if (firstSerie.dataLabels.isValue) { + builder.element('c:dLbls', nest: () async { + if (firstSerie.dataLabels.numberFormat != null && + firstSerie.dataLabels.numberFormat != '' && + firstSerie.dataLabels.numberFormat != 'General') { + builder.element('c:numFmt', nest: () async { + builder.attribute( + 'formatCode', firstSerie.dataLabels.numberFormat); + builder.attribute('sourceLinked', '0'); + }); + } + builder.element('c:spPr', nest: () async { + builder.element('a:noFill', nest: () async {}); + builder.element('a:ln', nest: () async { + builder.element('a:noFill', nest: () async {}); + }); + builder.element('a:effectLst', nest: () async {}); + }); + final ChartTextArea textArea = firstSerie.dataLabels.textArea; + _serializeChartTextAreaAsync(builder, textArea); + builder.element('c:showLegendKey', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:showVal', nest: () async { + builder.attribute('val', firstSerie.dataLabels.isValue ? 1 : 0); + }); + builder.element('c:showCatName', nest: () async { + builder.attribute( + 'val', firstSerie.dataLabels.isCategoryName ? 1 : 0); + }); + builder.element('c:showSerName', nest: () async { + builder.attribute( + 'val', firstSerie.dataLabels.isSeriesName ? 1 : 0); + }); + builder.element('c:showPercent', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:showBubbleSize', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:showLeaderLines', nest: () async { + builder.attribute('val', 0); + }); + }); + } + if (firstSerie._categoryLabels != null) { + final Range firstRange = firstSerie._chart._worksheet.getRangeByIndex( + firstSerie._categoryLabels!.row, + firstSerie._categoryLabels!.column); + builder.element('c:cat', nest: () async { + Worksheet tempSheet = firstSerie._chart._worksheet; + if (firstSerie._categoryLabels!.addressGlobal != '') { + for (final Worksheet sheet + in firstSerie._chart._worksheet.workbook.worksheets.innerList) { + if (firstSerie._categoryLabels!.addressGlobal + .contains(RegExp('${sheet.name}!')) || + firstSerie._categoryLabels!.addressGlobal + .contains(RegExp("${sheet.name}'!"))) { + tempSheet = sheet; + break; + } + } + } + if (firstRange.text == null && firstRange.number != null) { + builder.element('c:numRef', nest: () async { + builder.element('c:f', + nest: firstSerie._categoryLabels!.addressGlobal); + final Range firstRange = firstSerie._chart._worksheet + .getRangeByIndex(firstSerie._categoryLabels!.row, + firstSerie._categoryLabels!.column); + builder.element('c:numCache', nest: () async { + if (firstRange.numberFormat != null && + firstRange.numberFormat != 'General') { + builder.element('c:formatCode', + nest: firstRange.numberFormat); + } + _serializeNumCacheValuesAsync(builder, firstSerie, tempSheet); + }); + }); + } else { + _serializeStringReferenceAsync( + builder, + firstSerie._categoryLabels!.addressGlobal, + firstSerie, + 'cat', + tempSheet); + } + }); + } + if (firstSerie._values != null) { + builder.element('c:val', nest: () async { + builder.element('c:numRef', nest: () async { + builder.element('c:f', nest: firstSerie._values!.addressGlobal); + final Range firstRange = firstSerie._chart._worksheet + .getRangeByIndex( + firstSerie._values!.row, firstSerie._values!.column); + Worksheet tempSheet = firstSerie._chart._worksheet; + if (firstSerie._values!.addressGlobal != '') { + for (final Worksheet sheet in firstSerie + ._chart._worksheet.workbook.worksheets.innerList) { + if (firstSerie._values!.addressGlobal + .contains(RegExp('${sheet.name}!')) || + firstSerie._values!.addressGlobal + .contains(RegExp("${sheet.name}'!"))) { + tempSheet = sheet; + break; + } + } + } + builder.element('c:numCache', nest: () async { + if (firstRange.numberFormat != null && + firstRange.numberFormat != 'General') { + builder.element('c:formatCode', nest: firstRange.numberFormat); + } else { + builder.element('c:formatCode', nest: 'General'); + } + _serializeNumCacheValuesAsync(builder, firstSerie, tempSheet); + }); + }); + }); + } + if (firstSerie._serieType.toString().contains('line') || + firstSerie._serieType.toString().contains('stock')) { + builder.element('c:smooth', nest: () async { + builder.attribute('val', 0); + }); + } + }); + } + + /// serializes chart text area. + Future _serializeChartTextAreaAsync( + XmlBuilder builder, ChartTextArea textArea) async { + builder.element('c:txPr', nest: () async { + builder.element('a:bodyPr ', nest: () async {}); + builder.element('a:lstStyle', nest: () async {}); + builder.element('a:p', nest: () async { + builder.element('a:pPr', nest: () async { + builder.element('a:defRPr', nest: () async { + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + builder.attribute('baseline', '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element('a:solidFill', nest: () async { + builder.element('a:srgbClr', nest: () async { + builder.attribute('val', textArea.color.replaceAll('#', '')); + }); + }); + } + builder.element('a:latin', nest: () async { + builder.attribute('typeface', textArea.fontName); + }); + builder.element('a:ea', nest: () async { + builder.attribute('typeface', textArea.fontName); + }); + builder.element('a:cs', nest: () async { + builder.attribute('typeface', textArea.fontName); + }); + }); + }); + }); + }); + } + + /// serializes number cache values. + Future _serializeNumCacheValuesAsync( + XmlBuilder builder, ChartSerie firstSerie, Worksheet dataSheet) async { + final Range? serieRange = firstSerie._values; + if (serieRange != null) { + final int count = serieRange.count; + final int serieStartRow = serieRange.row; + final int serieStartColumn = serieRange.column; + builder.element('c:ptCount', nest: () async { + builder.attribute('val', count); + }); + int index = 0; + while (index < count) { + final double? value = dataSheet + .getRangeByIndex(serieStartRow + index, serieStartColumn) + .number; + if (value != null) { + builder.element('c:pt', nest: () async { + builder.attribute('idx', index); + builder.element('c:v', nest: value); + }); + } + index++; + } + } + } + + /// serializes string cache reference. + Future _serializeStringReferenceAsync(XmlBuilder builder, String range, + ChartSerie firstSerie, String tagName, Worksheet? dataSheet) async { + builder.element('c:strRef', nest: () async { + builder.element('c:f', nest: range); + builder.element('c:strCache', nest: () async { + if (tagName == 'cat') { + _serializeCategoryTagCacheValuesAsync(builder, firstSerie, dataSheet); + } else { + _serializeTextTagCacheValuesAsync(builder, firstSerie); + } + }); + }); + } + + /// serializes catergory cache values. + Future _serializeCategoryTagCacheValuesAsync( + XmlBuilder builder, ChartSerie firstSerie, Worksheet? dataSheet) async { + final Range? serieRange = firstSerie._categoryLabels; + if (serieRange != null) { + final int count = serieRange.count; + final int serieStartRow = serieRange.row; + final int serieStartColumn = serieRange.column; + builder.element('c:ptCount', nest: () async { + builder.attribute('val', count); + }); + int index = 0; + while (index < count) { + final String? value = dataSheet != null + ? dataSheet + .getRangeByIndex(serieStartRow + index, serieStartColumn) + .text + : ''; + if (value != null) { + builder.element('c:pt', nest: () async { + builder.attribute('idx', index); + builder.element('c:v', nest: value); + }); + } + index++; + } + } + } + + /// serializes text cache values. + Future _serializeTextTagCacheValuesAsync( + XmlBuilder builder, ChartSerie firstSerie) async { + builder.element('c:ptCount', nest: () async { + builder.attribute('val', 1); + }); + builder.element('c:pt', nest: () async { + builder.attribute('idx', 0); + if (firstSerie.name != null) { + builder.element('c:v', nest: firstSerie.name); + } + }); + } + + /// serializes fill for the charts. + Future _serializeFillAsync( + XmlBuilder builder, + ExcelChartLinePattern linePattern, + String? lineColor, + bool hasSerie) async { + builder.element('c:spPr', nest: () async { + if (lineColor == null) { + builder.element('a:solidFill', nest: () async { + builder.element('a:srgbClr', nest: () async { + builder.attribute('val', 'FFFFFF'); + }); + }); + } + builder.element('a:ln', nest: () async { + if (linePattern != ExcelChartLinePattern.none) { + if (!hasSerie || lineColor != null) { + builder.element('a:solidFill', nest: () async { + builder.element('a:srgbClr', nest: () async { + if (lineColor != null) { + builder.attribute('val', lineColor.replaceAll('#', '')); + } else { + builder.attribute('val', '0070C0'); + } + }); + }); + } + if (linePattern != ExcelChartLinePattern.solid) { + builder.element('a:prstDash', nest: () async { + builder.attribute('val', _linePattern[linePattern]); + }); + } + } else { + builder.element('a:noFill', nest: () async {}); + } + builder.element('a:round', nest: () async {}); + }); + }); + } + + /// serializes axies of the series. + Future _serializeAxesAsync(XmlBuilder builder, Chart chart) async { + if (chart._isCategoryAxisAvail) { + _serializeCategoryAxisAsync(builder, chart.primaryCategoryAxis); + } + if (chart._isValueAxisAvail) { + _serializeValueAxisAsync(builder, chart.primaryValueAxis); + } + } + + /// serializes empty charts. + Future _serializeEmptyChartAsync( + XmlBuilder builder, Chart chart) async { + builder.element('c:barChart', nest: () async { + builder.element('c:barDir', nest: () async { + builder.attribute('val', 'col'); + }); + builder.element('c:grouping', nest: () async { + builder.attribute('val', 'clustered'); + }); + builder.element('c:varyColors', nest: () async { + builder.attribute('val', 0); + }); + _serializeEmptyChartDataLabelsAsync(builder); + builder.element('c:gapWidth', nest: () async { + builder.attribute('val', 150); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 57253888); + }); + }); + } + + /// serializes category axis of the chart. + Future _serializeCategoryAxisAsync( + XmlBuilder builder, ChartCategoryAxis axis) async { + builder.element('c:catAx', nest: () async { + builder.element('c:axId', nest: () async { + builder.attribute('val', 59983360); + }); + builder.element('c:scaling', nest: () async { + builder.element('c:orientation', nest: () async { + builder.attribute('val', 'minMax'); + }); + }); + builder.element('c:delete', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:axPos', nest: () async { + builder.attribute('val', 'b'); + }); + if (axis._hasAxisTitle) { + _serializeChartTextAreaAsync(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element('c:numFmt', nest: () async { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }); + } + builder.element('c:majorTickMark', nest: () async { + builder.attribute('val', 'out'); + }); + builder.element('c:minorTickMark', nest: () async { + builder.attribute('val', 'none'); + }); + builder.element('c:tickLblPos', nest: () async { + builder.attribute('val', 'nextTo'); + }); + builder.element('c:spPr', nest: () async { + builder.element('a:ln', nest: () async {}); + }); + builder.element('c:crossAx', nest: () async { + builder.attribute('val', 57253888); + }); + builder.element('c:crosses', nest: () async { + builder.attribute('val', 'autoZero'); + }); + builder.element('c:auto', nest: () async { + builder.attribute('val', 1); + }); + builder.element('c:lblAlgn', nest: () async { + builder.attribute('val', 'ctr'); + }); + builder.element('c:lblOffset', nest: () async { + builder.attribute('val', 100); + }); + builder.element('c:noMultiLvlLbl', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:tickMarkSkip', nest: () async { + builder.attribute('val', 1); + }); + }); + } + + /// serializes Value axis of the chart. + Future _serializeValueAxisAsync( + XmlBuilder builder, ChartValueAxis axis) async { + builder.element('c:valAx', nest: () async { + builder.element('c:axId', nest: () async { + builder.attribute('val', 57253888); + }); + builder.element('c:scaling', nest: () async { + builder.element('c:orientation', nest: () async { + builder.attribute('val', 'minMax'); + }); + if (!axis._isAutoMax) { + builder.element('c:max', nest: () async { + builder.attribute('val', axis.maximumValue); + }); + } + if (axis._isAutoMin) { + builder.element('c:min', nest: () async { + builder.attribute('val', axis.minimumValue); + }); + } + }); + builder.element('c:delete', nest: () async { + builder.attribute('val', '0'); + }); + builder.element('c:axPos', nest: () async { + builder.attribute('val', 'l'); + }); + if (axis._hasAxisTitle) { + _serializeChartTextAreaAsync(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element('c:numFmt', nest: () async { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }); + } + if (axis.hasMajorGridLines) { + builder.element('c:majorGridlines', nest: () async {}); + } + builder.element('c:majorTickMark', nest: () async { + builder.attribute('val', 'out'); + }); + builder.element('c:minorTickMark', nest: () async { + builder.attribute('val', 'none'); + }); + builder.element('c:tickLblPos', nest: () async { + builder.attribute('val', 'nextTo'); + }); + builder.element('c:spPr', nest: () async { + builder.element('a:ln', nest: () async {}); + }); + builder.element('c:crossAx', nest: () async { + builder.attribute('val', 59983360); + }); + builder.element('c:crosses', nest: () async { + builder.attribute('val', 'autoZero'); + }); + final Chart chart = axis._parentChart; + final String strCrossBetween = + chart.primaryCategoryAxis._isBetween ? 'between' : 'midCat'; + builder.element('c:crossBetween', nest: () async { + builder.attribute('val', strCrossBetween); + }); + }); + } + + /// serializes empty chart's datalabels. + Future _serializeEmptyChartDataLabelsAsync(XmlBuilder builder) async { + builder.element('c:dLbls', nest: () async { + builder.element('c:showLegendKey', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:showVal', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:showCatName', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:showSerName', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:showPercent', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:showBubbleSize', nest: () async { + builder.attribute('val', 0); + }); + }); + } + + /// Add the workbook data with filename to ZipArchive. + Future _addToArchiveAsync(List data, String fileName) async { + final ArchiveFile item = ArchiveFile(fileName, data.length, data); + _workbook.archive.addFile(item); + } + + /// Serialize line 3D Chart. + Future _serializeLine3DChartAsync( + XmlBuilder builder, Chart chart) async { + late int gapdepth; + builder.element('c:line3DChart', nest: () async { + _serializeChartGroupingAsync(builder, chart); + builder.element('c:varyColors', nest: () async { + builder.attribute('val', 0); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + gapdepth = firstSerie.serieFormat.commonSerieOptions.gapDepth; + } + builder.element('c:gapDepth', nest: () async { + builder.attribute('val', gapdepth); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 57253888); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 63149376); + }); + }); + _serializeAxesAsync(builder, chart); + } + + ///Serialize view 3D + Future _serializeView3DAsync(XmlBuilder builder, Chart chart) async { + final ChartSeriesCollection firstSerie = chart.series; + + builder.element('c:view3D', nest: () async { + if (!chart._isdefaultElevation) { + builder.element('c:rotX', nest: () async { + builder.attribute('val', chart.elevation); + }); + } + if (firstSerie._innerList[0]._serieType == ExcelChartType.pie3D) { + for (int i = 0; i < chart.series.count; i++) { + chart.rotation = + chart.series[i].serieFormat.commonSerieOptions.firstSliceAngle; + } + } + if (!chart._isDefaultRotation) { + builder.element('c:rotY', nest: () async { + builder.attribute('val', chart.rotation); + }); + } + builder.element('c:depthPercent', nest: () async { + builder.attribute('val', chart.depthPercent); + }); + builder.element('c:rAngAx', nest: () async { + int defaultValue = 0; + + if (chart.rightAngleAxes || chart._isColumnOrBar) { + defaultValue = 1; + } + + builder.attribute('val', defaultValue); + }); + builder.element('perspective', nest: () async { + builder.attribute('val', chart.perspective * 2); + }); + }); + } + + /// Serialize bar3D chart. + Future _serializeBar3DChartAsync( + XmlBuilder builder, Chart chart) async { + late int gapdepth; + late int gapwidth; + builder.element('c:bar3DChart', nest: () async { + final String strDirection = + chart.chartType.toString().contains('bar') ? 'bar' : 'col'; + + builder.element('c:barDir', nest: () async { + builder.attribute('val', strDirection); + }); + + _serializeChartGroupingAsync(builder, chart); + builder.element('c:varyColors', nest: () async { + builder.attribute('val', 0); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + gapdepth = firstSerie.serieFormat.commonSerieOptions.gapDepth; + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + + builder.element('c:gapWidth', nest: () async { + builder.attribute('val', gapwidth); + }); + builder.element('c:gapDepth', nest: () async { + builder.attribute('val', gapdepth); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 57253888); + }); + }); + _serializeAxesAsync(builder, chart); + } + + // Serializes pie of pie or pie of bar chart. + Future _serializeOfPieChartAsync( + XmlBuilder builder, Chart chart) async { + late int gapwidth; + late int pieSecondSize; + final ExcelChartType type = chart.series[0]._serieType; + late String isPieOrBar; + if (type == ExcelChartType.pieOfPie) { + isPieOrBar = 'pie'; + } else if (type == ExcelChartType.pieBar) { + isPieOrBar = 'bar'; + } + builder.element('c:ofPieChart', nest: () async { + builder.element('c:ofPieType', nest: () async { + builder.attribute('val', isPieOrBar); + }); + builder.element('c:varyColors', nest: () async { + builder.attribute('val', 1); + }); + + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + pieSecondSize = firstSerie.serieFormat.commonSerieOptions.pieSecondSize; + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + + builder.element('c:gapWidth', nest: () async { + builder.attribute('val', gapwidth); + }); + builder.element('c:secondPieSize', nest: () async { + builder.attribute('val', pieSecondSize); + }); + builder.element('c:serLines', nest: () async { + builder.element('c:spPr', nest: () async { + builder.element('a:ln', nest: () async {}); + }); + }); + }); + } + + ///Serialize pie 3D chart. + Future _serializeOfPie3DChartAsync( + XmlBuilder builder, Chart chart) async { + builder.element('c:pie3DChart', nest: () async { + builder.element('c:varyColors', nest: () async { + builder.attribute('val', 1); + }); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + }); + } + + /// Serializes stock chart. + Future _serializeStockChartAsync( + XmlBuilder builder, Chart chart) async { + final ExcelChartType type = chart.series.innerList[0]._serieType; + if (type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) { + builder.element('c:barChart', nest: () async { + builder.element('c:barDir', nest: () async { + builder.attribute('val', 'col'); + }); + builder.element('c:grouping', nest: () async { + builder.attribute('val', 'clustered'); + }); + + builder.element('c:varyColors', nest: () async { + builder.attribute('val', 0); + }); + + final ChartSerie firstSerie = chart.series[0]; + _serializeSerieAsync(builder, firstSerie); + + builder.element('c:gapWidth', nest: () async { + builder.attribute( + 'val', firstSerie.serieFormat.commonSerieOptions.gapWidth); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 59983360); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 57253888); + }); + }); + } + builder.element('c:stockChart', nest: () async { + if (type == ExcelChartType.stockHighLowClose || + type == ExcelChartType.stockOpenHighLowClose) { + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + } else if (type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + for (int i = 0; i < chart.series.count; i++) { + if ((type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) && + chart.series.innerList[0] != chart.series.innerList[i]) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + } + } + + builder.element('c:hiLowLines', nest: () async { + builder.element('c:spPr', nest: () async { + builder.element('a:ln', nest: () async { + builder.element('a:solidFill', nest: () async { + builder.element('a:srgbClr', nest: () async { + builder.attribute('val', '000000'); + }); + }); + builder.element('a:prstDash', nest: () async { + builder.attribute('val', 'solid'); + }); + }); + }); + }); + if (type == ExcelChartType.stockOpenHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + builder.element('c:upDownBars', nest: () async { + builder.element('c:gapWidth', nest: () async { + builder.attribute('val', '100'); + }); + builder.element('c:upBars', nest: () async { + builder.element('c:spPr', nest: () async { + builder.element('a:solidFill', nest: () async { + builder.element('a:srgbClr', nest: () async { + builder.attribute('val', 'FFFFFF'); + }); + }); + }); + }); + builder.element('c:downBars', nest: () async { + builder.element('c:spPr', nest: () async { + builder.element('a:solidFill', nest: () async { + builder.element('a:srgbClr', nest: () async { + builder.attribute('val', '000000'); + }); + }); + }); + }); + }); + } + if (chart.series[0].serieFormat.markerStyle != + ExcelChartMarkerType.none) { + builder.element('c:marker', nest: () async { + builder.attribute('val', 1); + }); + } + + builder.element('c:axId', nest: () async { + builder.attribute('val', 62908672); + }); + builder.element('c:axId', nest: () async { + builder.attribute('val', 61870848); + }); + }); + if (type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) { + _serializeAxesforStockChartAsync( + builder, chart, 59983360, 57253888, true); + _serializeAxesforStockChartAsync( + builder, chart, 62908672, 61870848, false); + } else { + _serializeAxesforStockChartAsync( + builder, chart, 62908672, 61870848, true); + } + } + + ///serialize stock axes + Future _serializeAxesforStockChartAsync(XmlBuilder builder, Chart chart, + int axisId, int crossAx, bool isBar) async { + if (chart._isCategoryAxisAvail) { + _serializeCategoryAxisForStockAsync( + builder, chart.primaryCategoryAxis, axisId, crossAx, isBar); + } + if (chart._isValueAxisAvail) { + _serializeValueAxisForStockchartAsync( + builder, chart.primaryCategoryAxis, axisId, crossAx, isBar); + } + } + + ///Serialize catogory axis for stock chart + Future _serializeCategoryAxisForStockAsync(XmlBuilder builder, + ChartCategoryAxis axis, int axisId, int crossAx, bool isBar) async { + builder.element('c:catAx', nest: () async { + builder.element('c:axId', nest: () async { + builder.attribute('val', axisId); + }); + builder.element('c:scaling', nest: () async { + builder.element('c:orientation', nest: () async { + builder.attribute('val', 'minMax'); + }); + }); + int delete = 0; + String axpos = 'b'; + if (!isBar) { + delete = 1; + axpos = 't'; + } + builder.element('c:delete', nest: () async { + builder.attribute('val', delete); + }); + builder.element('c:axPos', nest: () async { + builder.attribute('val', axpos); + }); + if (axis._hasAxisTitle) { + _serializeChartTextAreaAsync(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element('c:numFmt', nest: () async { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }); + } + builder.element('c:majorTickMark', nest: () async { + builder.attribute('val', 'out'); + }); + builder.element('c:minorTickMark', nest: () async { + builder.attribute('val', 'none'); + }); + builder.element('c:tickLblPos', nest: () async { + builder.attribute('val', 'nextTo'); + }); + builder.element('c:spPr', nest: () async { + builder.element('a:ln', nest: () async { + if (!isBar) { + builder.attribute('w', 12700); + } + }); + }); + builder.element('c:crossAx', nest: () async { + builder.attribute('val', crossAx); + }); + builder.element('c:crosses', nest: () async { + builder.attribute('val', 'autoZero'); + }); + builder.element('c:auto', nest: () async { + builder.attribute('val', 1); + }); + builder.element('c:lblAlgn', nest: () async { + builder.attribute('val', 'ctr'); + }); + builder.element('c:lblOffset', nest: () async { + builder.attribute('val', 100); + }); + builder.element('c:noMultiLvlLbl', nest: () async { + builder.attribute('val', 0); + }); + builder.element('c:tickMarkSkip', nest: () async { + builder.attribute('val', 1); + }); + }); + } + + ///Serialize value axis for stock chart + Future _serializeValueAxisForStockchartAsync(XmlBuilder builder, + ChartCategoryAxis axis, int axisId, int crossAx, bool isBar) async { + builder.element('c:valAx', nest: () async { + builder.element('c:axId', nest: () async { + builder.attribute('val', crossAx); + }); + builder.element('c:scaling', nest: () async { + builder.element('c:orientation', nest: () async { + builder.attribute('val', 'minMax'); + }); + if (!axis._isAutoMax) { + builder.element('c:max', nest: () async { + builder.attribute('val', axis.maximumValue); + }); + } + if (axis._isAutoMin) { + builder.element('c:min', nest: () async { + builder.attribute('val', axis.minimumValue); + }); + } + }); + builder.element('c:delete', nest: () async { + builder.attribute('val', '0'); + }); + String axpos = 'l'; + + if (!isBar) { + axpos = 'r'; + } + + builder.element('c:axPos', nest: () async { + builder.attribute('val', axpos); + }); + + if (axpos == 'l') { + builder.element('c:majorGridlines', nest: () async {}); + } + + if (axis._hasAxisTitle) { + _serializeChartTextAreaAsync(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element('c:numFmt', nest: () async { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }); + } + if (axis.hasMajorGridLines) { + builder.element('c:majorGridlines', nest: () async {}); + } + builder.element('c:majorTickMark', nest: () async { + builder.attribute('val', 'out'); + }); + builder.element('c:minorTickMark', nest: () async { + builder.attribute('val', 'none'); + }); + builder.element('c:tickLblPos', nest: () async { + builder.attribute('val', 'nextTo'); + }); + builder.element('c:spPr', nest: () async { + builder.element('a:ln', nest: () async {}); + }); + builder.element('c:crossAx', nest: () async { + builder.attribute('val', axisId); + }); + String crosses = 'autoZero'; + if (!isBar) { + crosses = 'max'; + } + builder.element('c:crosses', nest: () async { + builder.attribute('val', crosses); + }); + final Chart chart = axis._parentChart; + final String strCrossBetween = + chart.primaryCategoryAxis._isBetween ? 'between' : 'midCat'; + builder.element('c:crossBetween', nest: () async { + builder.attribute('val', strCrossBetween); + }); + }); + } + + ///Serialize doughnut/doughnut_exploded charts + Future _serializedoughnutchartAsync( + XmlBuilder builder, Chart chart) async { + late int doughnutHoleSize; + late int firstSliceAngle; + + builder.element('c:doughnutChart', nest: () async { + builder.element('c:varyColors', nest: () async { + builder.attribute('val', 1); + }); + + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + firstSliceAngle = + firstSerie.serieFormat.commonSerieOptions.firstSliceAngle; + doughnutHoleSize = + firstSerie.serieFormat.commonSerieOptions.holeSizePercent; + } + builder.element('c:firstSliceAng', nest: () async { + builder.attribute('val', firstSliceAngle); + }); + builder.element('c:holeSize', nest: () async { + builder.attribute('val', doughnutHoleSize); + }); + }); + } + + ///Serialize marker for stock and line charts + Future _serializeMarkerAsync( + XmlBuilder builder, ChartSerie firstSerie) async { + final ExcelChartType type = firstSerie._serieType; + + if ((firstSerie.serieFormat.markerStyle == ExcelChartMarkerType.none) && + (type == ExcelChartType.line || + type == ExcelChartType.lineStacked || + type == ExcelChartType.lineStacked100 || + type == ExcelChartType.stockHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockOpenHighLowClose)) { + builder.element('c:marker', nest: () async { + builder.element('c:symbol', nest: () async { + builder.attribute('val', 'none'); + }); + }); + } else if ((firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.none) && + (type == ExcelChartType.lineMarkers || + type == ExcelChartType.lineMarkersStacked || + type == ExcelChartType.lineMarkersStacked100)) { + builder.element('c:marker', nest: () async { + builder.element('c:symbol', nest: () async { + builder.attribute('val', 'circle'); + }); + builder.element('c:size', nest: () async { + builder.attribute('val', '5'); + }); + }); + } else if (firstSerie.serieFormat.markerStyle != + ExcelChartMarkerType.none) { + final String markerBackgroundColor = + firstSerie.serieFormat.markerBackgroundColor.replaceAll('#', ''); + final String markerBorderColor = + firstSerie.serieFormat.markerBorderColor.replaceAll('#', ''); + late String exclMarkertype; + if (firstSerie.serieFormat.markerStyle == ExcelChartMarkerType.diamond) { + exclMarkertype = 'diamond'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.circle) { + exclMarkertype = 'circle'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.xSquare) { + exclMarkertype = 'x'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.dowJones) { + exclMarkertype = 'dot'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.square) { + exclMarkertype = 'square'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.plusSign) { + exclMarkertype = 'plus'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.standardDeviation) { + exclMarkertype = 'triangle'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.starSquare) { + exclMarkertype = 'star'; + } + + builder.element('c:marker', nest: () async { + builder.element('c:symbol', nest: () async { + builder.attribute('val', exclMarkertype); + }); + builder.element('c:size', nest: () async { + builder.attribute('val', '5'); + }); + builder.element('c:spPr', nest: () async { + builder.element('a:solidFill', nest: () async { + builder.element('a:srgbClr', nest: () async { + builder.attribute('val', markerBackgroundColor); + }); + }); + builder.element('a:ln', nest: () async { + builder.element('a:solidFill', nest: () async { + builder.element('a:srgbClr', nest: () async { + builder.attribute('val', markerBorderColor); + }); + }); + }); + }); + }); + } + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie.dart new file mode 100644 index 000000000..abad3b244 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie.dart @@ -0,0 +1,92 @@ +part of officechart; + +/// This class represents ChartSeries object. +class ChartSerie { + /// Create an instances of [ChartSerie] class. + ChartSerie(Worksheet worksheet, Chart chart) { + _worksheet = worksheet; + _chart = chart; + } + + /// Parent worksheet. + // ignore: unused_field + late Worksheet _worksheet; + + /// Parent chart. + late Chart _chart; + + /// Represent the serie name. + String? name; + + ///Represents chart series format + ChartSerieDataFormat? _chartSeriesDataFormat; + + /// serie name. + late int _index; + + /// serie name or formula. + // ignore: prefer_final_fields + String _nameOrFormula = ''; + + /// Check if the serie name is default. + // ignore: prefer_final_fields + bool _isDefaultName = false; + + /// Chart Range Values + Range? _values; + + /// Chart Range Values + Range? _categoryLabels; + + /// Chart DataLabels + ChartDataLabels? _dataLabels; + + /// Represents the PlotArea border line property + ExcelChartLinePattern linePattern = ExcelChartLinePattern.none; + + /// ChartArea border line color property + String? linePatternColor; + + /// Chart type for the series. + ExcelChartType get _serieType { + return _chart.chartType; + } + + ///Represents chart serie format. + ChartSerieDataFormat get serieFormat { + return _chartSeriesDataFormat ??= _ChartSerieDataFormatImpl(_chart); + } + + /// Gets chart text area object. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A1').text = 'Items'; + /// sheet.getRangeByName('B1').text = 'Count'; + /// sheet.getRangeByName('A2').text = 'Beverages'; + /// sheet.getRangeByName('A3').text = 'Condiments'; + /// sheet.getRangeByName('A4').text = 'Confections'; + /// sheet.getRangeByName('B2').number = 2776; + /// sheet.getRangeByName('B3').number = 1077; + /// sheet.getRangeByName('B4').number = 2287; + /// ChartCollection charts = new ChartCollection(sheet); + /// Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.line; + /// chart.dataRange = sheet.getRangeByName('A1:B4'); + /// ChartSerie serie = chart.series[0]; + /// serie.dataLabels.IsValue = true; + /// serie.dataLabels.textArea.size = 12; + /// serie.dataLabels.textArea.bold = true; + /// serie.dataLabels.textArea.color = '#0000A0'; + /// serie.linePattern = ExcelChartLinePattern.dash; + /// chart.isSeriesInRows = false; + /// sheet.charts = charts; + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + ChartDataLabels get dataLabels { + return _dataLabels ??= ChartDataLabels(this); + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie_dataformat.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie_dataformat.dart new file mode 100644 index 000000000..eac61821b --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie_dataformat.dart @@ -0,0 +1,19 @@ +part of officechart; + +/// Represents chart series format class +class ChartSerieDataFormat { + /// Represents background color of a marker. + late String markerBackgroundColor; + + /// Represents border color of a marker. + late String markerBorderColor; + + /// Represents the type of marker. + late ExcelChartMarkerType markerStyle; + + ///Represents the chart series options. + late ChartFormat commonSerieOptions; + + /// Represents the distance of a pie slice from the center of pie chart. + late int pieExplosionPercent; +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_series_collection.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_series_collection.dart new file mode 100644 index 000000000..561cd81c3 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_series_collection.dart @@ -0,0 +1,50 @@ +part of officechart; + +/// Represents the Chart serie collection. +class ChartSeriesCollection { + /// Create a instance of [ChartSeriesCollection] class. + ChartSeriesCollection(Worksheet worksheet, Chart chart) { + _worksheet = worksheet; + _chart = chart; + _innerList = []; + } + + /// Parent worksheet. + late Worksheet _worksheet; + + /// Parent chart. + late Chart _chart; + + /// Inner list. + late List _innerList; + + /// Represents parent worksheet. + Worksheet get worksheet { + return _worksheet; + } + + /// Represents the innerList. + List get innerList { + return _innerList; + } + + /// Returns the count of pivot reference collection. + int get count { + return _innerList.length; + } + + /// Indexer of the class + ChartSerie operator [](dynamic index) => innerList[index]; + + /// Add serie to the chart serie collection. + ChartSerie _add() { + final ChartSerie serie = ChartSerie(_worksheet, _chart); + innerList.add(serie); + return serie; + } + + /// Clear the innerList. + void _clear() { + _innerList.clear(); + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_text_area.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_text_area.dart new file mode 100644 index 000000000..f73372438 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_text_area.dart @@ -0,0 +1,94 @@ +part of officechart; + +/// Represent the Chart Text Area. +class ChartTextArea { + /// Create an instances of [ChartTextArea] class. + ChartTextArea(Object parent) { + _parent = parent; + if (_parent is Chart) { + _chart = _parent as Chart; + } else if (_parent is ChartCategoryAxis) { + _chart = (_parent as ChartCategoryAxis)._chart; + } else if (_parent is ChartValueAxis) { + _chart = (_parent as ChartValueAxis)._chart; + } else if (_parent is ChartSerie) { + _chart = (_parent as ChartSerie)._chart; + } + _createFont(); + } + + /// Represent the title name. + String? text; + + /// Represent the color. + String get color { + return _font.color; + } + + set color(String value) { + _font.color = value; + } + + /// Parent chart. + // ignore: unused_field + late Chart _chart; + + /// Parent object. + late Object _parent; + + /// Reprent the font. + late Font _font; + + /// Boolean value indicates whether other elements in chart can overlap this text area. + final bool _overlay = false; + + /// Gets bold. + bool get bold { + return _font.bold; + } + + /// Sets bold. + set bold(bool value) { + _font.bold = value; + } + + /// Gets italic. + bool get italic { + return _font.italic; + } + + /// Sets italic. + set italic(bool value) { + _font.italic = value; + } + + /// Gets font size. + double get size { + return _font.size; + } + + /// Sets font size. + set size(double value) { + _font.size = value; + } + + /// Gets font name. + String get fontName { + return _font.name; + } + + /// Sets font name. + set fontName(String value) { + _font.name = value; + } + + /// Indicates whether text area contains text. + bool get _hasText { + return text != null; + } + + /// Create a new font. + void _createFont() { + _font = Font(); + } +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_value_axis.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_value_axis.dart new file mode 100644 index 000000000..b6196deb2 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_value_axis.dart @@ -0,0 +1,18 @@ +part of officechart; + +/// Represents an axis on the chart. +class ChartValueAxis extends ChartAxis { + /// Create an instances of [ChartValueAxis] class. + ChartValueAxis(Worksheet worksheet, Chart chart) { + _worksheet = worksheet; + _chart = chart; + super._parentChart = _chart; + } + + // Parent worksheet. + // ignore: unused_field + late Worksheet _worksheet; + + // Parent chart. + late Chart _chart; +} diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chartserie_dataformat_impl.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chartserie_dataformat_impl.dart new file mode 100644 index 000000000..129e9c006 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chartserie_dataformat_impl.dart @@ -0,0 +1,92 @@ +part of officechart; + +///Represents chart series format +class _ChartSerieDataFormatImpl implements ChartSerieDataFormat { + /// Create a instance of [_ChartSerieDataFormatImpl] class. + _ChartSerieDataFormatImpl(Chart chart) { + _markerBackgroundColor = '#000000'; + _markerForegroundColor = '#000000'; + _markerStyle = ExcelChartMarkerType.none; + _chart = chart; + } + + /// Represents the marker background fill color. + late String _markerBackgroundColor; + + /// Represents the marker Border fill color. + late String _markerForegroundColor; + + ///Represnets the marker style for a series in a line chart,stock chart. + late ExcelChartMarkerType _markerStyle; + + ///Represents chart format + ChartFormat? _chartFormat; + + /// Parent chart. + late Chart _chart; + + ///Represents the distance of pie slice from center of pie. + int _percent = 0; + + ///Get marker background color + @override + String get markerBackgroundColor { + return _markerBackgroundColor; + } + + ///Set marker background color + @override + set markerBackgroundColor(String value) { + _markerBackgroundColor = value; + } + + ///Get marker Border color + @override + String get markerBorderColor { + return _markerForegroundColor; + } + + ///Set marker Border color + @override + set markerBorderColor(String value) { + _markerForegroundColor = value; + } + + ///Set marker style + @override + set markerStyle(ExcelChartMarkerType value) { + _markerStyle = value; + } + + ///Get marker style. + @override + ExcelChartMarkerType get markerStyle { + return _markerStyle; + } + + ///Get chart format + @override + ChartFormat get commonSerieOptions { + _chartFormat ??= _ChartformatImpl(_chart); + return _chartFormat!; + } + + ///Set chart format + @override + set commonSerieOptions(ChartFormat? chartFormat) {} + + /// Get the distance of pie slice from center of pie. + @override + int get pieExplosionPercent { + return _percent; + } + + /// Set the distance of pie slice from center of pie. + @override + set pieExplosionPercent(int value) { + if (value < 0 || value > 400) { + throw Exception('percent'); + } + _percent = value; + } +} From 411d2cc712b86cab18af9cad4639f997f5514bc4 Mon Sep 17 00:00:00 2001 From: LokeshPalani Date: Wed, 20 Mar 2024 16:36:50 +0530 Subject: [PATCH 11/11] Added the missed files in the office core --- .../example/lib/helper/save_file_mobile.dart | 37 +++ .../example/lib/helper/save_file_web.dart | 15 ++ .../example/lib/main.dart | 227 ++++++++++++++++++ .../lib/officecore.dart | 3 + .../lib/src/built_in_properties.dart | 35 +++ 5 files changed, 317 insertions(+) create mode 100644 packages/syncfusion_flutter_officecore/example/lib/helper/save_file_mobile.dart create mode 100644 packages/syncfusion_flutter_officecore/example/lib/helper/save_file_web.dart create mode 100644 packages/syncfusion_flutter_officecore/example/lib/main.dart create mode 100644 packages/syncfusion_flutter_officecore/lib/officecore.dart create mode 100644 packages/syncfusion_flutter_officecore/lib/src/built_in_properties.dart diff --git a/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_mobile.dart new file mode 100644 index 000000000..c7ddbff8b --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_mobile.dart @@ -0,0 +1,37 @@ +import 'dart:io'; + +import 'package:open_file/open_file.dart' as open_file; +import 'package:path_provider/path_provider.dart' as path_provider; +// ignore: depend_on_referenced_packages +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; + +///To save the Excel file in the device +///To save the Excel file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + //Get the storage folder location using path_provider package. + String? path; + if (Platform.isAndroid || + Platform.isIOS || + Platform.isLinux || + Platform.isWindows) { + final Directory directory = + await path_provider.getApplicationSupportDirectory(); + path = directory.path; + } else { + path = await PathProviderPlatform.instance.getApplicationSupportPath(); + } + final File file = + File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); + await file.writeAsBytes(bytes, flush: true); + if (Platform.isAndroid || Platform.isIOS) { + //Launch the file (used open_file package) + await open_file.OpenFile.open('$path/$fileName'); + } else if (Platform.isWindows) { + await Process.run('start', ['$path\\$fileName'], runInShell: true); + } else if (Platform.isMacOS) { + await Process.run('open', ['$path/$fileName'], runInShell: true); + } else if (Platform.isLinux) { + await Process.run('xdg-open', ['$path/$fileName'], + runInShell: true); + } +} diff --git a/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_web.dart b/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_web.dart new file mode 100644 index 000000000..a455ce682 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_web.dart @@ -0,0 +1,15 @@ +///Dart imports +import 'dart:async'; +import 'dart:convert'; +// ignore: avoid_web_libraries_in_flutter +import 'dart:html'; + +///To save the Excel file in the device +///To save the Excel file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + AnchorElement( + href: + 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') + ..setAttribute('download', fileName) + ..click(); +} diff --git a/packages/syncfusion_flutter_officecore/example/lib/main.dart b/packages/syncfusion_flutter_officecore/example/lib/main.dart new file mode 100644 index 000000000..0805b93bc --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/lib/main.dart @@ -0,0 +1,227 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_xlsio/xlsio.dart' hide Column; + +//Local imports +import 'helper/save_file_mobile.dart' + if (dart.library.html) 'helper/save_file_web.dart'; + +void main() { + runApp(CreateExcelWidget()); +} + +/// Represents the XlsIO widget class. +class CreateExcelWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: CreateExcelStatefulWidget(title: 'Create Excel document'), + ); + } +} + +/// Represents the XlsIO stateful widget class. +class CreateExcelStatefulWidget extends StatefulWidget { + /// Initalize the instance of the [CreateExcelStatefulWidget] class. + const CreateExcelStatefulWidget({Key? key, required this.title}) + : super(key: key); + + /// title. + final String title; + @override + // ignore: library_private_types_in_public_api + _CreateExcelState createState() => _CreateExcelState(); +} + +class _CreateExcelState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + style: TextButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.lightBlue, + disabledForegroundColor: Colors.grey, + ), + onPressed: generateExcel, + child: const Text('Generate Excel'), + ) + ], + ), + ), + ); + } + + Future generateExcel() async { + //Create a Excel document. + + //Creating a workbook. + final Workbook workbook = Workbook(); + //Accessing via index + final Worksheet sheet = workbook.worksheets[0]; + sheet.showGridlines = false; + + // Enable calculation for worksheet. + sheet.enableSheetCalculations(); + + //Set data in the worksheet. + sheet.getRangeByName('A1').columnWidth = 4.82; + sheet.getRangeByName('B1:C1').columnWidth = 13.82; + sheet.getRangeByName('D1').columnWidth = 13.20; + sheet.getRangeByName('E1').columnWidth = 7.50; + sheet.getRangeByName('F1').columnWidth = 9.73; + sheet.getRangeByName('G1').columnWidth = 8.82; + sheet.getRangeByName('H1').columnWidth = 4.46; + + sheet.getRangeByName('A1:H1').cellStyle.backColor = '#333F4F'; + sheet.getRangeByName('A1:H1').merge(); + sheet.getRangeByName('B4:D6').merge(); + + sheet.getRangeByName('B4').setText('Invoice'); + sheet.getRangeByName('B4').cellStyle.fontSize = 32; + + sheet.getRangeByName('B8').setText('BILL TO:'); + sheet.getRangeByName('B8').cellStyle.fontSize = 9; + sheet.getRangeByName('B8').cellStyle.bold = true; + + sheet.getRangeByName('B9').setText('Abraham Swearegin'); + sheet.getRangeByName('B9').cellStyle.fontSize = 12; + + sheet + .getRangeByName('B10') + .setText('United States, California, San Mateo,'); + sheet.getRangeByName('B10').cellStyle.fontSize = 9; + + sheet.getRangeByName('B11').setText('9920 BridgePointe Parkway,'); + sheet.getRangeByName('B11').cellStyle.fontSize = 9; + + sheet.getRangeByName('B12').setNumber(9365550136); + sheet.getRangeByName('B12').cellStyle.fontSize = 9; + sheet.getRangeByName('B12').cellStyle.hAlign = HAlignType.left; + + final Range range1 = sheet.getRangeByName('F8:G8'); + final Range range2 = sheet.getRangeByName('F9:G9'); + final Range range3 = sheet.getRangeByName('F10:G10'); + final Range range4 = sheet.getRangeByName('F11:G11'); + final Range range5 = sheet.getRangeByName('F12:G12'); + + range1.merge(); + range2.merge(); + range3.merge(); + range4.merge(); + range5.merge(); + + sheet.getRangeByName('F8').setText('INVOICE#'); + range1.cellStyle.fontSize = 8; + range1.cellStyle.bold = true; + range1.cellStyle.hAlign = HAlignType.right; + + sheet.getRangeByName('F9').setNumber(2058557939); + range2.cellStyle.fontSize = 9; + range2.cellStyle.hAlign = HAlignType.right; + + sheet.getRangeByName('F10').setText('DATE'); + range3.cellStyle.fontSize = 8; + range3.cellStyle.bold = true; + range3.cellStyle.hAlign = HAlignType.right; + + sheet.getRangeByName('F11').dateTime = DateTime(2020, 08, 31); + sheet.getRangeByName('F11').numberFormat = + r'[$-x-sysdate]dddd, mmmm dd, yyyy'; + range4.cellStyle.fontSize = 9; + range4.cellStyle.hAlign = HAlignType.right; + + range5.cellStyle.fontSize = 8; + range5.cellStyle.bold = true; + range5.cellStyle.hAlign = HAlignType.right; + + final Range range6 = sheet.getRangeByName('B15:G15'); + range6.cellStyle.fontSize = 10; + range6.cellStyle.bold = true; + + sheet.getRangeByIndex(15, 2).setText('Code'); + sheet.getRangeByIndex(16, 2).setText('CA-1098'); + sheet.getRangeByIndex(17, 2).setText('LJ-0192'); + sheet.getRangeByIndex(18, 2).setText('So-B909-M'); + sheet.getRangeByIndex(19, 2).setText('FK-5136'); + sheet.getRangeByIndex(20, 2).setText('HL-U509'); + + sheet.getRangeByIndex(15, 3).setText('Description'); + sheet.getRangeByIndex(16, 3).setText('AWC Logo Cap'); + sheet.getRangeByIndex(17, 3).setText('Long-Sleeve Logo Jersey, M'); + sheet.getRangeByIndex(18, 3).setText('Mountain Bike Socks, M'); + sheet.getRangeByIndex(19, 3).setText('ML Fork'); + sheet.getRangeByIndex(20, 3).setText('Sports-100 Helmet, Black'); + + sheet.getRangeByIndex(15, 3, 15, 4).merge(); + sheet.getRangeByIndex(16, 3, 16, 4).merge(); + sheet.getRangeByIndex(17, 3, 17, 4).merge(); + sheet.getRangeByIndex(18, 3, 18, 4).merge(); + sheet.getRangeByIndex(19, 3, 19, 4).merge(); + sheet.getRangeByIndex(20, 3, 20, 4).merge(); + + sheet.getRangeByIndex(15, 5).setText('Quantity'); + sheet.getRangeByIndex(16, 5).setNumber(2); + sheet.getRangeByIndex(17, 5).setNumber(3); + sheet.getRangeByIndex(18, 5).setNumber(2); + sheet.getRangeByIndex(19, 5).setNumber(6); + sheet.getRangeByIndex(20, 5).setNumber(1); + + sheet.getRangeByIndex(15, 6).setText('Price'); + sheet.getRangeByIndex(16, 6).setNumber(8.99); + sheet.getRangeByIndex(17, 6).setNumber(49.99); + sheet.getRangeByIndex(18, 6).setNumber(9.50); + sheet.getRangeByIndex(19, 6).setNumber(175.49); + sheet.getRangeByIndex(20, 6).setNumber(34.99); + + sheet.getRangeByIndex(15, 7).setText('Total'); + sheet.getRangeByIndex(16, 7).setFormula('=E16*F16+(E16*F16)'); + sheet.getRangeByIndex(17, 7).setFormula('=E17*F17+(E17*F17)'); + sheet.getRangeByIndex(18, 7).setFormula('=E18*F18+(E18*F18)'); + sheet.getRangeByIndex(19, 7).setFormula('=E19*F19+(E19*F19)'); + sheet.getRangeByIndex(20, 7).setFormula('=E20*F20+(E20*F20)'); + sheet.getRangeByIndex(15, 6, 20, 7).numberFormat = r'$#,##0.00'; + + sheet.getRangeByName('E15:G15').cellStyle.hAlign = HAlignType.right; + sheet.getRangeByName('B15:G15').cellStyle.fontSize = 10; + sheet.getRangeByName('B15:G15').cellStyle.bold = true; + sheet.getRangeByName('B16:G20').cellStyle.fontSize = 9; + + sheet.getRangeByName('E22:G22').merge(); + sheet.getRangeByName('E22:G22').cellStyle.hAlign = HAlignType.right; + sheet.getRangeByName('E23:G24').merge(); + + final Range range7 = sheet.getRangeByName('E22'); + final Range range8 = sheet.getRangeByName('E23'); + range7.setText('TOTAL'); + range7.cellStyle.fontSize = 8; + range8.setFormula('=SUM(G16:G20)'); + range8.numberFormat = r'$#,##0.00'; + range8.cellStyle.fontSize = 24; + range8.cellStyle.hAlign = HAlignType.right; + range8.cellStyle.bold = true; + + sheet.getRangeByIndex(26, 1).text = + '800 Interchange Blvd, Suite 2501, Austin, TX 78721 | support@adventure-works.com'; + sheet.getRangeByIndex(26, 1).cellStyle.fontSize = 8; + + final Range range9 = sheet.getRangeByName('A26:H27'); + range9.cellStyle.backColor = '#ACB9CA'; + range9.merge(); + range9.cellStyle.hAlign = HAlignType.center; + range9.cellStyle.vAlign = VAlignType.center; + + //Save and launch the excel. + final List bytes = workbook.saveAsStream(); + //Dispose the document. + workbook.dispose(); + + await saveAndLaunchFile(bytes, 'Invoice.xlsx'); + } +} diff --git a/packages/syncfusion_flutter_officecore/lib/officecore.dart b/packages/syncfusion_flutter_officecore/lib/officecore.dart new file mode 100644 index 000000000..11baf04c2 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/lib/officecore.dart @@ -0,0 +1,3 @@ +library officecore; + +export 'src/built_in_properties.dart' show BuiltInProperties; diff --git a/packages/syncfusion_flutter_officecore/lib/src/built_in_properties.dart b/packages/syncfusion_flutter_officecore/lib/src/built_in_properties.dart new file mode 100644 index 000000000..b1b9aaca1 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/lib/src/built_in_properties.dart @@ -0,0 +1,35 @@ +///Represent the document properties in a MS Excel Document. +class BuiltInProperties { + /// Gets or Sets author of the document. + String? author; + + /// Gets or Sets comments of the document. + String? comments; + + /// Gets or Sets category of the document. + String? category; + + /// Gets or Sets company of the document. + String? company; + + /// Gets or Sets manager of the document. + String? manager; + + /// Gets or Sets subject of the document. + String? subject; + + /// Gets or Sets title of the document. + String? title; + + /// Gets or Sets creation date of the document. + DateTime? createdDate = DateTime.now(); + + /// Gets or Sets modified date of the document. + DateTime? modifiedDate = DateTime.now(); + + /// Gets or Sets tags of the document. + String? tags; + + /// Gets or Sets status of the document. + String? status; +}