2020-03-19 15:07:56 -03:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-03-19 15:07:56 -03:00
*/
2021-04-19 18:47:29 -03:00
# include <AK/Base64.h>
2020-04-15 11:55:36 -03:00
# include <AK/Checked.h>
2020-03-19 15:07:56 -03:00
# include <LibGfx/Bitmap.h>
2021-04-19 18:47:29 -03:00
# include <LibGfx/PNGWriter.h>
2021-09-24 08:49:57 -03:00
# include <LibWeb/CSS/StyleComputer.h>
2020-03-19 15:07:56 -03:00
# include <LibWeb/DOM/Document.h>
2020-07-26 10:08:16 -03:00
# include <LibWeb/HTML/CanvasRenderingContext2D.h>
# include <LibWeb/HTML/HTMLCanvasElement.h>
2020-11-22 11:53:01 -03:00
# include <LibWeb/Layout/CanvasBox.h>
2020-03-19 15:07:56 -03:00
2020-07-28 13:20:36 -03:00
namespace Web : : HTML {
2020-03-19 15:07:56 -03:00
2020-04-15 07:12:19 -03:00
static constexpr auto max_canvas_area = 16384 * 16384 ;
2022-02-18 17:00:52 -03:00
HTMLCanvasElement : : HTMLCanvasElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
2021-02-07 07:20:15 -03:00
: HTMLElement ( document , move ( qualified_name ) )
2020-03-19 15:07:56 -03:00
{
2022-09-03 13:43:24 -03:00
set_prototype ( & document . window ( ) . cached_web_prototype ( " HTMLCanvasElement " ) ) ;
2020-03-19 15:07:56 -03:00
}
2022-03-14 16:21:51 -03:00
HTMLCanvasElement : : ~ HTMLCanvasElement ( ) = default ;
2020-03-19 15:07:56 -03:00
2022-09-02 10:53:02 -03:00
void HTMLCanvasElement : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
m_context . visit (
[ & ] ( JS : : NonnullGCPtr < CanvasRenderingContext2D > & context ) {
visitor . visit ( context . ptr ( ) ) ;
} ,
[ & ] ( JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > & context ) {
visitor . visit ( context . ptr ( ) ) ;
} ,
[ ] ( Empty ) {
} ) ;
}
2020-06-21 10:37:13 -03:00
unsigned HTMLCanvasElement : : width ( ) const
2020-03-19 15:07:56 -03:00
{
2020-06-21 10:37:13 -03:00
return attribute ( HTML : : AttributeNames : : width ) . to_uint ( ) . value_or ( 300 ) ;
2020-03-19 15:07:56 -03:00
}
2020-06-21 10:37:13 -03:00
unsigned HTMLCanvasElement : : height ( ) const
2020-03-19 15:07:56 -03:00
{
2020-06-21 10:37:13 -03:00
return attribute ( HTML : : AttributeNames : : height ) . to_uint ( ) . value_or ( 150 ) ;
2020-03-19 15:07:56 -03:00
}
2022-06-04 00:22:42 -03:00
void HTMLCanvasElement : : reset_context_to_default_state ( )
{
m_context . visit (
2022-09-02 10:53:02 -03:00
[ ] ( JS : : NonnullGCPtr < CanvasRenderingContext2D > & context ) {
2022-06-04 00:22:42 -03:00
context - > reset_to_default_state ( ) ;
} ,
2022-09-02 10:53:02 -03:00
[ ] ( JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > & ) {
2022-06-04 00:22:42 -03:00
TODO ( ) ;
} ,
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2021-11-12 20:54:21 -03:00
void HTMLCanvasElement : : set_width ( unsigned value )
{
set_attribute ( HTML : : AttributeNames : : width , String : : number ( value ) ) ;
2022-04-10 22:05:19 -03:00
m_bitmap = nullptr ;
2022-06-04 00:22:42 -03:00
reset_context_to_default_state ( ) ;
2021-11-12 20:54:21 -03:00
}
void HTMLCanvasElement : : set_height ( unsigned value )
{
set_attribute ( HTML : : AttributeNames : : height , String : : number ( value ) ) ;
2022-04-10 22:05:19 -03:00
m_bitmap = nullptr ;
2022-06-04 00:22:42 -03:00
reset_context_to_default_state ( ) ;
2021-11-12 20:54:21 -03:00
}
2022-02-05 09:17:01 -03:00
RefPtr < Layout : : Node > HTMLCanvasElement : : create_layout_node ( NonnullRefPtr < CSS : : StyleProperties > style )
2020-03-19 15:07:56 -03:00
{
2021-04-23 11:46:57 -03:00
return adopt_ref ( * new Layout : : CanvasBox ( document ( ) , * this , move ( style ) ) ) ;
2020-03-19 15:07:56 -03:00
}
2022-06-04 00:22:42 -03:00
HTMLCanvasElement : : HasOrCreatedContext HTMLCanvasElement : : create_2d_context ( )
{
if ( ! m_context . has < Empty > ( ) )
2022-09-02 10:53:02 -03:00
return m_context . has < JS : : NonnullGCPtr < CanvasRenderingContext2D > > ( ) ? HasOrCreatedContext : : Yes : HasOrCreatedContext : : No ;
2022-06-04 00:22:42 -03:00
2022-09-02 10:53:02 -03:00
m_context = CanvasRenderingContext2D : : create ( window ( ) , * this ) ;
2022-06-04 00:22:42 -03:00
return HasOrCreatedContext : : Yes ;
}
JS : : ThrowCompletionOr < HTMLCanvasElement : : HasOrCreatedContext > HTMLCanvasElement : : create_webgl_context ( JS : : Value options )
2020-03-19 15:07:56 -03:00
{
2022-06-04 00:22:42 -03:00
if ( ! m_context . has < Empty > ( ) )
2022-09-02 10:53:02 -03:00
return m_context . has < JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > > ( ) ? HasOrCreatedContext : : Yes : HasOrCreatedContext : : No ;
2022-06-04 00:22:42 -03:00
2022-09-02 10:53:02 -03:00
auto maybe_context = TRY ( WebGL : : WebGLRenderingContext : : create ( window ( ) , * this , options ) ) ;
2022-06-04 00:22:42 -03:00
if ( ! maybe_context )
return HasOrCreatedContext : : No ;
2022-09-02 10:53:02 -03:00
m_context = JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > ( * maybe_context ) ;
2022-06-04 00:22:42 -03:00
return HasOrCreatedContext : : Yes ;
2020-03-19 15:07:56 -03:00
}
2022-06-04 00:22:42 -03: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 07:12:19 -03:00
{
2022-06-04 00:22:42 -03: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 )
2022-09-02 10:53:02 -03:00
return JS : : make_handle ( * m_context . get < JS : : NonnullGCPtr < HTML : : CanvasRenderingContext2D > > ( ) ) ;
2022-06-04 00:22:42 -03:00
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 )
2022-09-02 10:53:02 -03:00
return JS : : make_handle ( * m_context . get < JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > > ( ) ) ;
2022-06-04 00:22:42 -03:00
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 11:55:36 -03:00
Checked < size_t > area = width ;
area * = height ;
if ( area . has_overflow ( ) ) {
2021-01-09 10:02:45 -03:00
dbgln ( " Refusing to create {}x{} canvas (overflow) " , width , height ) ;
2020-04-15 07:12:19 -03:00
return { } ;
}
2020-04-15 11:55:36 -03:00
if ( area . value ( ) > max_canvas_area ) {
2021-01-09 10:02:45 -03:00
dbgln ( " Refusing to create {}x{} canvas (exceeds maximum size) " , width , height ) ;
2020-04-15 07:12:19 -03:00
return { } ;
}
2020-06-21 10:37:13 -03:00
return Gfx : : IntSize ( width , height ) ;
2020-04-15 07:12:19 -03:00
}
2022-06-04 00:22:42 -03:00
bool HTMLCanvasElement : : create_bitmap ( size_t minimum_width , size_t minimum_height )
2020-03-19 15:07:56 -03:00
{
2022-06-04 00:22:42 -03:00
auto size = bitmap_size_for_canvas ( * this , minimum_width , minimum_height ) ;
2020-04-15 07:12:19 -03:00
if ( size . is_empty ( ) ) {
m_bitmap = nullptr ;
return false ;
2020-03-19 15:07:56 -03:00
}
2021-11-06 15:30:59 -03: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 11:55:36 -03:00
return m_bitmap ;
2020-03-19 15:07:56 -03:00
}
2022-04-01 14:58:27 -03:00
String HTMLCanvasElement : : to_data_url ( String const & type , [[maybe_unused]] Optional < double > quality ) const
2021-04-19 18:47:29 -03:00
{
if ( ! m_bitmap )
return { } ;
if ( type ! = " image/png " )
return { } ;
auto encoded_bitmap = Gfx : : PNGWriter : : encode ( * m_bitmap ) ;
2021-09-12 18:33:23 -03:00
return AK : : URL : : create_with_data ( type , encode_base64 ( encoded_bitmap ) , true ) . to_string ( ) ;
2021-04-19 18:47:29 -03:00
}
2022-06-04 00:22:42 -03:00
void HTMLCanvasElement : : present ( )
{
m_context . visit (
2022-09-02 10:53:02 -03:00
[ ] ( JS : : NonnullGCPtr < CanvasRenderingContext2D > & ) {
2022-06-04 00:22:42 -03:00
// Do nothing, CRC2D writes directly to the canvas bitmap.
} ,
2022-09-02 10:53:02 -03:00
[ ] ( JS : : NonnullGCPtr < WebGL : : WebGLRenderingContext > & context ) {
2022-06-04 00:22:42 -03:00
context - > present ( ) ;
} ,
[ ] ( Empty ) {
// Do nothing.
} ) ;
}
2020-03-19 15:07:56 -03:00
}