2018-01-11 03:49:32 +08:00
namespace UglyToad.PdfPig.Core
2017-11-23 02:41:34 +08:00
{
using System ;
2023-02-01 01:07:07 +08:00
using System.Collections.Generic ;
2018-01-07 04:51:20 +08:00
using System.Diagnostics.Contracts ;
2019-12-22 02:09:49 +08:00
using System.Linq ;
2023-02-01 01:07:07 +08:00
using static UglyToad . PdfPig . Core . PdfSubpath ;
2017-11-23 02:41:34 +08:00
/// <summary>
/// Specifies the conversion from the transformed coordinate space to the original untransformed coordinate space.
/// </summary>
2023-08-27 01:34:48 +08:00
public readonly struct TransformationMatrix
2017-11-23 02:41:34 +08:00
{
2019-01-04 06:20:53 +08:00
/// <summary>
/// The default <see cref="TransformationMatrix"/>.
/// </summary>
2023-08-27 01:34:48 +08:00
public static readonly TransformationMatrix Identity = new TransformationMatrix ( 1 , 0 , 0 ,
0 , 1 , 0 ,
0 , 0 , 1 ) ;
2019-08-21 05:51:00 +08:00
/// <summary>
/// Create a new <see cref="TransformationMatrix"/> with the X and Y translation values set.
/// </summary>
2019-12-22 02:09:49 +08:00
public static TransformationMatrix GetTranslationMatrix ( double x , double y ) = > new TransformationMatrix ( 1 , 0 , 0 ,
2019-08-21 05:51:00 +08:00
0 , 1 , 0 ,
x , y , 1 ) ;
2023-08-27 01:34:48 +08:00
2020-01-22 21:28:47 +08:00
/// <summary>
/// Create a new <see cref="TransformationMatrix"/> with the X and Y scaling values set.
/// </summary>
2023-11-04 03:24:02 +08:00
public static TransformationMatrix GetScaleMatrix ( double scaleX , double scaleY ) = > new TransformationMatrix (
scaleX , 0 , 0 ,
2020-01-22 21:28:47 +08:00
0 , scaleY , 0 ,
0 , 0 , 1 ) ;
/// <summary>
/// Create a new <see cref="TransformationMatrix"/> with the X and Y scaling values set.
/// </summary>
public static TransformationMatrix GetRotationMatrix ( double degreesCounterclockwise )
{
double cos ;
double sin ;
2023-03-14 00:50:58 +08:00
var deg = degreesCounterclockwise % 360 ;
if ( deg < 0 )
{
deg + = 360 ;
}
switch ( deg )
2020-01-22 21:28:47 +08:00
{
case 0 :
case 360 :
cos = 1 ;
sin = 0 ;
break ;
case 90 :
cos = 0 ;
sin = 1 ;
break ;
case 180 :
cos = - 1 ;
sin = 0 ;
break ;
case 270 :
cos = 0 ;
sin = - 1 ;
break ;
default :
cos = Math . Cos ( degreesCounterclockwise * ( Math . PI / 180 ) ) ;
sin = Math . Sin ( degreesCounterclockwise * ( Math . PI / 180 ) ) ;
break ;
}
return new TransformationMatrix ( cos , sin , 0 ,
- sin , cos , 0 ,
0 , 0 , 1 ) ;
}
2017-11-23 02:41:34 +08:00
2019-12-22 02:09:49 +08:00
private readonly double row1 ;
private readonly double row2 ;
private readonly double row3 ;
2017-11-23 02:41:34 +08:00
2017-12-29 00:58:52 +08:00
/// <summary>
2019-08-21 05:51:00 +08:00
/// The value at (0, 0) - The scale for the X dimension.
2017-12-29 00:58:52 +08:00
/// </summary>
2019-12-22 02:09:49 +08:00
public readonly double A ;
2023-11-04 03:24:02 +08:00
2019-01-04 06:20:53 +08:00
/// <summary>
/// The value at (0, 1).
/// </summary>
2019-12-22 02:09:49 +08:00
public readonly double B ;
2023-11-04 03:24:02 +08:00
2019-01-04 06:20:53 +08:00
/// <summary>
/// The value at (1, 0).
/// </summary>
2019-12-22 02:09:49 +08:00
public readonly double C ;
2023-11-04 03:24:02 +08:00
2017-12-29 00:58:52 +08:00
/// <summary>
2019-08-21 05:51:00 +08:00
/// The value at (1, 1) - The scale for the Y dimension.
2017-12-29 00:58:52 +08:00
/// </summary>
2019-12-22 02:09:49 +08:00
public readonly double D ;
2023-11-04 03:24:02 +08:00
2019-01-04 06:20:53 +08:00
/// <summary>
/// The value at (2, 0) - translation in X.
/// </summary>
2019-12-22 02:09:49 +08:00
public readonly double E ;
2023-11-04 03:24:02 +08:00
2019-01-04 06:20:53 +08:00
/// <summary>
/// The value at (2, 1) - translation in Y.
/// </summary>
2019-12-22 02:09:49 +08:00
public readonly double F ;
2023-08-27 01:34:48 +08:00
2019-01-04 06:20:53 +08:00
/// <summary>
/// Get the value at the specific row and column.
/// </summary>
2019-12-22 02:09:49 +08:00
public double this [ int row , int col ]
2017-11-30 06:55:53 +08:00
{
get
{
if ( row > = Rows )
{
2019-01-04 06:20:53 +08:00
throw new ArgumentOutOfRangeException ( nameof ( row ) , $"The transformation matrix only contains {Rows} rows and is zero indexed, you tried to access row {row}." ) ;
2017-11-30 06:55:53 +08:00
}
if ( row < 0 )
{
2019-01-04 06:20:53 +08:00
throw new ArgumentOutOfRangeException ( nameof ( row ) , "Cannot access negative rows in a matrix." ) ;
2017-11-30 06:55:53 +08:00
}
if ( col > = Columns )
{
2019-01-04 06:20:53 +08:00
throw new ArgumentOutOfRangeException ( nameof ( col ) , $"The transformation matrix only contains {Columns} columns and is zero indexed, you tried to access column {col}." ) ;
2017-11-30 06:55:53 +08:00
}
if ( col < 0 )
{
2019-01-04 06:20:53 +08:00
throw new ArgumentOutOfRangeException ( nameof ( col ) , "Cannot access negative columns in a matrix." ) ;
2017-11-30 06:55:53 +08:00
}
2019-08-21 05:51:00 +08:00
switch ( row )
2017-11-30 06:55:53 +08:00
{
2019-08-21 05:51:00 +08:00
case 0 :
2023-10-19 06:44:11 +08:00
{
switch ( col )
2019-08-21 05:51:00 +08:00
{
2023-10-19 06:44:11 +08:00
case 0 :
return A ;
case 1 :
return B ;
case 2 :
return row1 ;
default :
2019-08-21 05:51:00 +08:00
throw new ArgumentOutOfRangeException ( $"Trying to access {row}, {col} which was not in the value array." ) ;
}
2023-10-19 06:44:11 +08:00
}
2019-08-21 05:51:00 +08:00
case 1 :
2023-10-19 06:44:11 +08:00
{
switch ( col )
2019-08-21 05:51:00 +08:00
{
2023-10-19 06:44:11 +08:00
case 0 :
return C ;
case 1 :
return D ;
case 2 :
return row2 ;
default :
2019-08-21 05:51:00 +08:00
throw new ArgumentOutOfRangeException ( $"Trying to access {row}, {col} which was not in the value array." ) ;
}
2023-10-19 06:44:11 +08:00
}
2019-08-21 05:51:00 +08:00
case 2 :
2023-10-19 06:44:11 +08:00
{
switch ( col )
2019-08-21 05:51:00 +08:00
{
2023-10-19 06:44:11 +08:00
case 0 :
return E ;
case 1 :
return F ;
case 2 :
return row3 ;
default :
2019-08-21 05:51:00 +08:00
throw new ArgumentOutOfRangeException ( $"Trying to access {row}, {col} which was not in the value array." ) ;
}
2023-10-19 06:44:11 +08:00
}
2019-08-21 05:51:00 +08:00
default :
throw new ArgumentOutOfRangeException ( $"Trying to access {row}, {col} which was not in the value array." ) ;
2017-11-30 06:55:53 +08:00
}
}
}
2019-01-04 06:20:53 +08:00
/// <summary>
/// The number of rows in the matrix.
/// </summary>
2017-11-30 06:55:53 +08:00
public const int Rows = 3 ;
2019-01-04 06:20:53 +08:00
/// <summary>
/// The number of columns in the matrix.
/// </summary>
2017-11-30 06:55:53 +08:00
public const int Columns = 3 ;
2019-08-21 05:51:00 +08:00
2019-01-04 06:20:53 +08:00
/// <summary>
/// Create a new <see cref="TransformationMatrix"/>.
/// </summary>
/// <param name="value">The 9 values of the matrix.</param>
2019-12-22 02:09:49 +08:00
public TransformationMatrix ( double [ ] value ) : this ( value [ 0 ] , value [ 1 ] , value [ 2 ] , value [ 3 ] , value [ 4 ] , value [ 5 ] , value [ 6 ] , value [ 7 ] , value [ 8 ] )
2017-11-23 02:41:34 +08:00
{
2019-08-21 05:51:00 +08:00
}
2017-11-23 02:41:34 +08:00
2019-08-21 05:51:00 +08:00
/// <summary>
/// Create a new <see cref="TransformationMatrix"/>.
/// </summary>
2019-12-22 02:09:49 +08:00
public TransformationMatrix ( double a , double b , double r1 , double c , double d , double r2 , double e , double f , double r3 )
2019-08-21 05:51:00 +08:00
{
A = a ;
B = b ;
row1 = r1 ;
C = c ;
D = d ;
row2 = r2 ;
E = e ;
F = f ;
row3 = r3 ;
2017-11-23 02:41:34 +08:00
}
2019-08-21 05:51:00 +08:00
2019-01-04 06:20:53 +08:00
/// <summary>
/// Transform a point using this transformation matrix.
/// </summary>
/// <param name="original">The original point.</param>
/// <returns>A new point which is the result of applying this transformation matrix.</returns>
[Pure]
2017-11-23 02:41:34 +08:00
public PdfPoint Transform ( PdfPoint original )
{
2023-11-04 03:24:02 +08:00
( double x , double y ) xy = Transform ( original . X , original . Y ) ;
return new PdfPoint ( xy . x , xy . y ) ;
}
2017-11-23 02:41:34 +08:00
2023-11-04 03:24:02 +08:00
/// <summary>
/// Transform a point using this transformation matrix.
/// </summary>
/// <param name="x">The original point X coordinate.</param>
/// <param name="y">The original point Y coordinate.</param>
/// <returns>A new point which is the result of applying this transformation matrix.</returns>
[Pure]
public ( double x , double y ) Transform ( double x , double y )
{
return ( A * x + C * y + E , B * x + D * y + F ) ;
2017-11-23 02:41:34 +08:00
}
2017-12-01 07:34:38 +08:00
2019-01-04 06:20:53 +08:00
/// <summary>
/// Transform an X coordinate using this transformation matrix.
/// </summary>
/// <param name="x">The X coordinate.</param>
/// <returns>The transformed X coordinate.</returns>
2018-11-25 19:37:00 +08:00
[Pure]
2020-01-05 06:39:13 +08:00
public double TransformX ( double x )
2018-11-25 19:37:00 +08:00
{
var xt = A * x + C * 0 + E ;
return xt ;
}
2019-01-04 06:20:53 +08:00
/// <summary>
/// Transform a rectangle using this transformation matrix.
/// </summary>
/// <param name="original">The original rectangle.</param>
/// <returns>A new rectangle which is the result of applying this transformation matrix.</returns>
2018-03-31 06:16:54 +08:00
[Pure]
public PdfRectangle Transform ( PdfRectangle original )
{
return new PdfRectangle (
2018-11-25 19:37:00 +08:00
Transform ( original . TopLeft ) ,
Transform ( original . TopRight ) ,
Transform ( original . BottomLeft ) ,
Transform ( original . BottomRight )
2018-03-31 06:16:54 +08:00
) ;
}
2023-02-01 01:07:07 +08:00
/// <summary>
/// Transform a subpath using this transformation matrix.
/// </summary>
/// <param name="subpath">The original subpath.</param>
/// <returns>A new subpath which is the result of applying this transformation matrix.</returns>
public PdfSubpath Transform ( PdfSubpath subpath )
{
var trSubpath = new PdfSubpath ( ) ;
foreach ( var c in subpath . Commands )
{
if ( c is Move move )
{
var loc = Transform ( move . Location ) ;
trSubpath . MoveTo ( loc . X , loc . Y ) ;
}
else if ( c is Line line )
{
//var from = Transform(line.From);
var to = Transform ( line . To ) ;
trSubpath . LineTo ( to . X , to . Y ) ;
}
else if ( c is BezierCurve curve )
{
var first = Transform ( curve . FirstControlPoint ) ;
var second = Transform ( curve . SecondControlPoint ) ;
var end = Transform ( curve . EndPoint ) ;
trSubpath . BezierCurveTo ( first . X , first . Y , second . X , second . Y , end . X , end . Y ) ;
}
else if ( c is Close )
{
trSubpath . CloseSubpath ( ) ;
}
else
{
throw new Exception ( "Unknown PdfSubpath type" ) ;
}
}
return trSubpath ;
}
/// <summary>
/// Transform a path using this transformation matrix.
/// </summary>
/// <param name="path">The original path.</param>
/// <returns>A new path which is the result of applying this transformation matrix.</returns>
public IEnumerable < PdfSubpath > Transform ( IEnumerable < PdfSubpath > path )
{
foreach ( var subpath in path )
{
yield return Transform ( subpath ) ;
}
}
2020-01-05 06:39:13 +08:00
/// <summary>
/// Generate a <see cref="TransformationMatrix"/> translated by the specified amount.
/// </summary>
2019-08-21 05:51:00 +08:00
[Pure]
2020-01-05 06:39:13 +08:00
public TransformationMatrix Translate ( double x , double y )
2019-08-21 05:51:00 +08:00
{
var a = A ;
var b = B ;
var r1 = row1 ;
var c = C ;
var d = D ;
var r2 = row2 ;
var e = ( x * A ) + ( y * C ) + E ;
var f = ( x * B ) + ( y * D ) + F ;
var r3 = ( x * row1 ) + ( y * row2 ) + row3 ;
return new TransformationMatrix ( a , b , r1 ,
c , d , r2 ,
e , f , r3 ) ;
}
2019-01-04 06:20:53 +08:00
/// <summary>
/// Create a new <see cref="TransformationMatrix"/> from the 6 values provided in the default PDF order.
/// </summary>
2019-12-22 02:09:49 +08:00
public static TransformationMatrix FromValues ( double a , double b , double c , double d , double e , double f )
2019-08-21 05:51:00 +08:00
= > new TransformationMatrix ( a , b , 0 , c , d , 0 , e , f , 1 ) ;
/// <summary>
/// Create a new <see cref="TransformationMatrix"/> from the 4 values provided in the default PDF order.
/// </summary>
2019-12-22 02:09:49 +08:00
public static TransformationMatrix FromValues ( double a , double b , double c , double d )
2019-08-21 05:51:00 +08:00
= > new TransformationMatrix ( a , b , 0 , c , d , 0 , 0 , 0 , 1 ) ;
2019-01-04 06:20:53 +08:00
2019-12-22 02:09:49 +08:00
/// <summary>
/// Create a new <see cref="TransformationMatrix"/> from the values.
/// </summary>
/// <param name="values">Either all 9 values of the matrix, 6 values in the default PDF order or the 4 values of the top left square.</param>
/// <returns></returns>
public static TransformationMatrix FromArray ( double [ ] values )
2017-11-23 02:41:34 +08:00
{
if ( values . Length = = 9 )
{
return new TransformationMatrix ( values ) ;
}
if ( values . Length = = 6 )
{
2019-08-21 05:51:00 +08:00
return new TransformationMatrix ( values [ 0 ] , values [ 1 ] , 0 ,
2017-11-23 02:41:34 +08:00
values [ 2 ] , values [ 3 ] , 0 ,
2019-08-21 05:51:00 +08:00
values [ 4 ] , values [ 5 ] , 1 ) ;
2017-11-23 02:41:34 +08:00
}
2018-04-29 21:42:54 +08:00
if ( values . Length = = 4 )
{
2019-08-21 05:51:00 +08:00
return new TransformationMatrix ( values [ 0 ] , values [ 1 ] , 0 ,
2018-04-29 21:42:54 +08:00
values [ 2 ] , values [ 3 ] , 0 ,
2019-08-21 05:51:00 +08:00
0 , 0 , 1 ) ;
2018-04-29 21:42:54 +08:00
}
2017-11-23 02:41:34 +08:00
throw new ArgumentException ( "The array must either define all 9 elements of the matrix or all 6 key elements. Instead array was: " + values ) ;
}
2019-01-04 06:20:53 +08:00
/// <summary>
/// Multiplies one transformation matrix by another without modifying either matrix. Order is: (this * matrix).
/// </summary>
/// <param name="matrix">The matrix to multiply</param>
/// <returns>The resulting matrix.</returns>
[Pure]
2017-11-30 06:55:53 +08:00
public TransformationMatrix Multiply ( TransformationMatrix matrix )
{
2019-08-21 05:51:00 +08:00
var a = ( A * matrix . A ) + ( B * matrix . C ) + ( row1 * matrix . E ) ;
var b = ( A * matrix . B ) + ( B * matrix . D ) + ( row1 * matrix . F ) ;
var r1 = ( A * matrix . row1 ) + ( B * matrix . row2 ) + ( row1 * matrix . row3 ) ;
2017-11-30 06:55:53 +08:00
2019-08-21 05:51:00 +08:00
var c = ( C * matrix . A ) + ( D * matrix . C ) + ( row2 * matrix . E ) ;
var d = ( C * matrix . B ) + ( D * matrix . D ) + ( row2 * matrix . F ) ;
var r2 = ( C * matrix . row1 ) + ( D * matrix . row2 ) + ( row2 * matrix . row3 ) ;
2017-11-30 06:55:53 +08:00
2019-08-21 05:51:00 +08:00
var e = ( E * matrix . A ) + ( F * matrix . C ) + ( row3 * matrix . E ) ;
var f = ( E * matrix . B ) + ( F * matrix . D ) + ( row3 * matrix . F ) ;
var r3 = ( E * matrix . row1 ) + ( F * matrix . row2 ) + ( row3 * matrix . row3 ) ;
2017-11-30 06:55:53 +08:00
2023-08-27 01:34:48 +08:00
return new TransformationMatrix ( a , b , r1 ,
2023-11-04 03:24:02 +08:00
c , d , r2 ,
2019-08-21 05:51:00 +08:00
e , f , r3 ) ;
2017-11-30 06:55:53 +08:00
}
2019-01-04 06:20:53 +08:00
/// <summary>
/// Multiplies the matrix by a scalar value without modifying this matrix.
/// </summary>
/// <param name="scalar">The value to multiply.</param>
/// <returns>A new matrix which is multiplied by the scalar value.</returns>
[Pure]
2019-12-22 02:09:49 +08:00
public TransformationMatrix Multiply ( double scalar )
2018-03-31 06:16:54 +08:00
{
2019-08-21 05:51:00 +08:00
return new TransformationMatrix ( A * scalar , B * scalar , row1 * scalar ,
C * scalar , D * scalar , row2 * scalar ,
E * scalar , F * scalar , row3 * scalar ) ;
2018-03-31 06:16:54 +08:00
}
2020-02-08 18:05:13 +08:00
/// <summary>
/// Get the inverse of the current matrix.
2020-02-01 01:53:10 +08:00
/// </summary>
public TransformationMatrix Inverse ( )
{
var a = ( D * row3 - row2 * F ) ;
var c = - ( C * row3 - row2 * E ) ;
var e = ( C * F - D * E ) ;
var b = - ( B * row3 - row1 * F ) ;
var d = ( A * row3 - row1 * E ) ;
var f = - ( A * F - B * E ) ;
var r1 = ( B * row2 - row1 * D ) ;
var r2 = - ( A * row2 - row1 * C ) ;
var r3 = ( A * D - B * C ) ;
var det = A * a + B * c + row1 * e ;
2020-02-08 18:05:13 +08:00
return new TransformationMatrix ( a / det , b / det , r1 / det ,
c / det , d / det , r2 / det ,
e / det , f / det , r3 / det ) ;
2020-02-01 01:53:10 +08:00
}
2019-01-04 06:20:53 +08:00
/// <summary>
/// Get the X scaling component of the current matrix.
/// </summary>
2019-12-22 02:09:49 +08:00
/// <returns>The scaling factor for the x dimension in this matrix.</returns>
internal double GetScalingFactorX ( )
2018-01-03 06:23:08 +08:00
{
var xScale = A ;
2018-01-07 20:37:48 +08:00
/ *
2018-01-03 06:23:08 +08:00
* BM : if the trm is rotated , the calculation is a little more complicated
*
* The rotation matrix multiplied with the scaling matrix is :
* ( x 0 0 ) ( cos sin 0 ) ( x * cos x * sin 0 )
* ( 0 y 0 ) * ( - sin cos 0 ) = ( - y * sin y * cos 0 )
* ( 0 0 1 ) ( 0 0 1 ) ( 0 0 1 )
*
* So , if you want to deduce x from the matrix you take
* M ( 0 , 0 ) = x * cos and M ( 0 , 1 ) = x * sin and use the theorem of Pythagoras
*
* sqrt ( M ( 0 , 0 ) ^ 2 + M ( 0 , 1 ) ^ 2 ) =
* sqrt ( x2 * cos2 + x2 * sin2 ) =
2018-01-07 20:37:48 +08:00
* sqrt ( x2 * ( cos2 + sin2 ) ) = ( here is the trick cos2 + sin2 = 1 )
2018-01-03 06:23:08 +08:00
* sqrt ( x2 ) =
* abs ( x )
* /
2019-12-22 02:09:49 +08:00
if ( ! ( B = = 0 & & C = = 0 ) )
2018-01-03 06:23:08 +08:00
{
2019-12-22 02:09:49 +08:00
xScale = Math . Sqrt ( A * A + B * B ) ;
2018-01-03 06:23:08 +08:00
}
return xScale ;
}
2019-01-04 06:20:53 +08:00
/// <inheritdoc />
2017-11-30 06:55:53 +08:00
public override bool Equals ( object obj )
{
if ( ! ( obj is TransformationMatrix m ) )
{
return false ;
}
return Equals ( this , m ) ;
}
2019-01-04 06:20:53 +08:00
/// <summary>
/// Determines whether 2 transformation matrices are equal.
/// </summary>
2017-11-30 06:55:53 +08:00
public static bool Equals ( TransformationMatrix a , TransformationMatrix b )
{
2019-01-04 06:20:53 +08:00
for ( var i = 0 ; i < Rows ; i + + )
2017-11-30 06:55:53 +08:00
{
2019-01-04 06:20:53 +08:00
for ( var j = 0 ; j < Columns ; j + + )
2017-11-30 06:55:53 +08:00
{
if ( a [ i , j ] ! = b [ i , j ] )
{
return false ;
}
}
}
return true ;
}
2019-01-04 06:20:53 +08:00
/// <inheritdoc />
2017-12-01 07:49:53 +08:00
public override int GetHashCode ( )
{
2019-08-21 05:51:00 +08:00
var hashCode = 472622392 ;
hashCode = hashCode * - 1521134295 + row1 . GetHashCode ( ) ;
hashCode = hashCode * - 1521134295 + row2 . GetHashCode ( ) ;
hashCode = hashCode * - 1521134295 + row3 . GetHashCode ( ) ;
hashCode = hashCode * - 1521134295 + A . GetHashCode ( ) ;
hashCode = hashCode * - 1521134295 + B . GetHashCode ( ) ;
hashCode = hashCode * - 1521134295 + C . GetHashCode ( ) ;
hashCode = hashCode * - 1521134295 + D . GetHashCode ( ) ;
hashCode = hashCode * - 1521134295 + E . GetHashCode ( ) ;
hashCode = hashCode * - 1521134295 + F . GetHashCode ( ) ;
2017-12-01 07:49:53 +08:00
return hashCode ;
}
2019-01-04 06:20:53 +08:00
/// <inheritdoc />
2017-11-23 02:41:34 +08:00
public override string ToString ( )
{
2019-08-21 05:51:00 +08:00
return $"{A}, {B}, {row1}\r\n{C}, {D}, {row2}\r\n{E}, {F}, {row3}" ;
2017-12-02 22:57:44 +08:00
}
2023-08-27 01:34:48 +08:00
/// <inheritdoc/>
public static bool operator = = ( TransformationMatrix left , TransformationMatrix right )
{
return left . Equals ( right ) ;
}
/// <inheritdoc/>
public static bool operator ! = ( TransformationMatrix left , TransformationMatrix right )
{
return ! ( left = = right ) ;
}
2017-11-23 02:41:34 +08:00
}
}