2020-03-19 19:07:56 +01:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-03-19 19:07:56 +01:00
*/
2021-04-19 23:47:29 +02:00
# include <AK/Base64.h>
2020-04-15 16:55:36 +02:00
# include <AK/Checked.h>
2020-03-19 19:07:56 +01:00
# include <LibGfx/Bitmap.h>
2021-04-19 23:47:29 +02:00
# include <LibGfx/PNGWriter.h>
2021-09-24 13:49:57 +02:00
# include <LibWeb/CSS/StyleComputer.h>
2020-03-19 19:07:56 +01:00
# include <LibWeb/DOM/Document.h>
2020-07-26 15:08:16 +02:00
# include <LibWeb/HTML/CanvasRenderingContext2D.h>
# include <LibWeb/HTML/HTMLCanvasElement.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/CanvasBox.h>
2020-03-19 19:07:56 +01:00
2020-07-28 18:20:36 +02:00
namespace Web : : HTML {
2020-03-19 19:07:56 +01:00
2020-04-15 12:12:19 +02:00
static constexpr auto max_canvas_area = 16384 * 16384 ;
2022-02-18 21:00:52 +01:00
HTMLCanvasElement : : HTMLCanvasElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
2021-02-07 11:20:15 +01:00
: HTMLElement ( document , move ( qualified_name ) )
2020-03-19 19:07:56 +01:00
{
}
2022-03-14 13:21:51 -06:00
HTMLCanvasElement : : ~ HTMLCanvasElement ( ) = default ;
2020-03-19 19:07:56 +01:00
2020-06-21 15:37:13 +02:00
unsigned HTMLCanvasElement : : width ( ) const
2020-03-19 19:07:56 +01:00
{
2020-06-21 15:37:13 +02:00
return attribute ( HTML : : AttributeNames : : width ) . to_uint ( ) . value_or ( 300 ) ;
2020-03-19 19:07:56 +01:00
}
2020-06-21 15:37:13 +02:00
unsigned HTMLCanvasElement : : height ( ) const
2020-03-19 19:07:56 +01:00
{
2020-06-21 15:37:13 +02:00
return attribute ( HTML : : AttributeNames : : height ) . to_uint ( ) . value_or ( 150 ) ;
2020-03-19 19:07:56 +01:00
}
2022-06-04 04:22:42 +01:00
void HTMLCanvasElement : : reset_context_to_default_state ( )
{
m_context . visit (
[ ] ( NonnullRefPtr < CanvasRenderingContext2D > & context ) {
context - > reset_to_default_state ( ) ;
} ,
[ ] ( NonnullRefPtr < WebGL : : WebGLRenderingContext > & ) {
TODO ( ) ;
} ,
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2021-11-13 00:54:21 +01:00
void HTMLCanvasElement : : set_width ( unsigned value )
{
set_attribute ( HTML : : AttributeNames : : width , String : : number ( value ) ) ;
2022-04-11 03:05:19 +02:00
m_bitmap = nullptr ;
2022-06-04 04:22:42 +01:00
reset_context_to_default_state ( ) ;
2021-11-13 00:54:21 +01:00
}
void HTMLCanvasElement : : set_height ( unsigned value )
{
set_attribute ( HTML : : AttributeNames : : height , String : : number ( value ) ) ;
2022-04-11 03:05:19 +02:00
m_bitmap = nullptr ;
2022-06-04 04:22:42 +01:00
reset_context_to_default_state ( ) ;
2021-11-13 00:54:21 +01:00
}
2022-02-05 13:17:01 +01:00
RefPtr < Layout : : Node > HTMLCanvasElement : : create_layout_node ( NonnullRefPtr < CSS : : StyleProperties > style )
2020-03-19 19:07:56 +01:00
{
2021-04-23 16:46:57 +02:00
return adopt_ref ( * new Layout : : CanvasBox ( document ( ) , * this , move ( style ) ) ) ;
2020-03-19 19:07:56 +01:00
}
2022-06-04 04:22:42 +01:00
HTMLCanvasElement : : HasOrCreatedContext HTMLCanvasElement : : create_2d_context ( )
{
if ( ! m_context . has < Empty > ( ) )
return m_context . has < NonnullRefPtr < CanvasRenderingContext2D > > ( ) ? HasOrCreatedContext : : Yes : HasOrCreatedContext : : No ;
m_context = CanvasRenderingContext2D : : create ( * this ) ;
return HasOrCreatedContext : : Yes ;
}
JS : : ThrowCompletionOr < HTMLCanvasElement : : HasOrCreatedContext > HTMLCanvasElement : : create_webgl_context ( JS : : Value options )
2020-03-19 19:07:56 +01:00
{
2022-06-04 04:22:42 +01:00
if ( ! m_context . has < Empty > ( ) )
return m_context . has < NonnullRefPtr < WebGL : : WebGLRenderingContext > > ( ) ? HasOrCreatedContext : : Yes : HasOrCreatedContext : : No ;
auto maybe_context = TRY ( WebGL : : WebGLRenderingContext : : create ( * this , options ) ) ;
if ( ! maybe_context )
return HasOrCreatedContext : : No ;
m_context = maybe_context . release_nonnull ( ) ;
return HasOrCreatedContext : : Yes ;
2020-03-19 19:07:56 +01:00
}
2022-06-04 04:22:42 +01:00
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext
JS : : ThrowCompletionOr < HTMLCanvasElement : : RenderingContext > HTMLCanvasElement : : get_context ( String const & type , JS : : Value options )
2020-04-15 12:12:19 +02:00
{
2022-06-04 04:22:42 +01:00
// 1. If options is not an object, then set options to null.
if ( ! options . is_object ( ) )
options = JS : : js_null ( ) ;
// 2. Set options to the result of converting options to a JavaScript value.
// NOTE: No-op.
// 3. Run the steps in the cell of the following table whose column header matches this canvas element's canvas context mode and whose row header matches contextId:
// NOTE: See the spec for the full table.
if ( type = = " 2d " sv ) {
if ( create_2d_context ( ) = = HasOrCreatedContext : : Yes )
return m_context ;
return Empty { } ;
}
// NOTE: The WebGL spec says "experimental-webgl" is also acceptable and must be equivalent to "webgl". Other engines accept this, so we do too.
if ( type . is_one_of ( " webgl " sv , " experimental-webgl " sv ) ) {
if ( TRY ( create_webgl_context ( options ) ) = = HasOrCreatedContext : : Yes )
return m_context ;
return Empty { } ;
}
return Empty { } ;
}
static Gfx : : IntSize bitmap_size_for_canvas ( HTMLCanvasElement const & canvas , size_t minimum_width , size_t minimum_height )
{
auto width = max ( canvas . width ( ) , minimum_width ) ;
auto height = max ( canvas . height ( ) , minimum_height ) ;
2020-04-15 16:55:36 +02:00
Checked < size_t > area = width ;
area * = height ;
if ( area . has_overflow ( ) ) {
2021-01-09 14:02:45 +01:00
dbgln ( " Refusing to create {}x{} canvas (overflow) " , width , height ) ;
2020-04-15 12:12:19 +02:00
return { } ;
}
2020-04-15 16:55:36 +02:00
if ( area . value ( ) > max_canvas_area ) {
2021-01-09 14:02:45 +01:00
dbgln ( " Refusing to create {}x{} canvas (exceeds maximum size) " , width , height ) ;
2020-04-15 12:12:19 +02:00
return { } ;
}
2020-06-21 15:37:13 +02:00
return Gfx : : IntSize ( width , height ) ;
2020-04-15 12:12:19 +02:00
}
2022-06-04 04:22:42 +01:00
bool HTMLCanvasElement : : create_bitmap ( size_t minimum_width , size_t minimum_height )
2020-03-19 19:07:56 +01:00
{
2022-06-04 04:22:42 +01:00
auto size = bitmap_size_for_canvas ( * this , minimum_width , minimum_height ) ;
2020-04-15 12:12:19 +02:00
if ( size . is_empty ( ) ) {
m_bitmap = nullptr ;
return false ;
2020-03-19 19:07:56 +01:00
}
2021-11-06 19:30:59 +01:00
if ( ! m_bitmap | | m_bitmap - > size ( ) ! = size ) {
auto bitmap_or_error = Gfx : : Bitmap : : try_create ( Gfx : : BitmapFormat : : BGRA8888 , size ) ;
if ( bitmap_or_error . is_error ( ) )
return false ;
m_bitmap = bitmap_or_error . release_value_but_fixme_should_propagate_errors ( ) ;
}
2020-04-15 16:55:36 +02:00
return m_bitmap ;
2020-03-19 19:07:56 +01:00
}
2022-04-01 20:58:27 +03:00
String HTMLCanvasElement : : to_data_url ( String const & type , [[maybe_unused]] Optional < double > quality ) const
2021-04-19 23:47:29 +02:00
{
if ( ! m_bitmap )
return { } ;
if ( type ! = " image/png " )
return { } ;
auto encoded_bitmap = Gfx : : PNGWriter : : encode ( * m_bitmap ) ;
2021-09-13 00:33:23 +03:00
return AK : : URL : : create_with_data ( type , encode_base64 ( encoded_bitmap ) , true ) . to_string ( ) ;
2021-04-19 23:47:29 +02:00
}
2022-06-04 04:22:42 +01:00
void HTMLCanvasElement : : present ( )
{
m_context . visit (
[ ] ( NonnullRefPtr < CanvasRenderingContext2D > & ) {
// Do nothing, CRC2D writes directly to the canvas bitmap.
} ,
[ ] ( NonnullRefPtr < WebGL : : WebGLRenderingContext > & context ) {
context - > present ( ) ;
} ,
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2020-03-19 19:07:56 +01:00
}